Merge "mediawiki.page.patrol: Use this.href instead of $(this).attr('href')"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 19 May 2016 17:45:24 +0000 (17:45 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 19 May 2016 17:45:24 +0000 (17:45 +0000)
83 files changed:
.stylelintrc [new file with mode: 0644]
Gruntfile.js
autoload.php
includes/DefaultSettings.php
includes/MediaWikiServices.php
includes/Services/SalvageableService.php [new file with mode: 0644]
includes/Services/ServiceContainer.php
includes/Setup.php
includes/actions/Action.php
includes/api/ApiSetNotificationTimestamp.php
includes/api/i18n/fr.json
includes/api/i18n/pl.json
includes/api/i18n/zh-hans.json
includes/config/ConfigFactory.php
includes/content/ContentHandler.php
includes/deferred/LinksUpdate.php
includes/htmlform/HTMLForm.php
includes/page/Article.php
includes/page/WikiPage.php
includes/specials/SpecialUndelete.php
includes/user/User.php
languages/i18n/ar.json
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bgn.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/he.json
languages/i18n/ia.json
languages/i18n/jv.json
languages/i18n/ko.json
languages/i18n/pl.json
languages/i18n/pms.json
languages/i18n/qqq.json
languages/i18n/ru.json
languages/i18n/zh-hans.json
maintenance/storage/recompressTracked.php
mw-config/config-cc.css
mw-config/config.css
package.json
resources/src/jquery.tipsy/jquery.tipsy.css
resources/src/jquery/jquery.arrowSteps.css
resources/src/jquery/jquery.badge.css
resources/src/jquery/jquery.suggestions.css
resources/src/mediawiki.action/mediawiki.action.history.css
resources/src/mediawiki.action/mediawiki.action.history.diff.css
resources/src/mediawiki.action/mediawiki.action.view.filepage.css
resources/src/mediawiki.action/mediawiki.action.view.postEdit.css
resources/src/mediawiki.legacy/oldshared.css
resources/src/mediawiki.legacy/shared.css
resources/src/mediawiki.skinning/content.css
resources/src/mediawiki.skinning/content.parsoid.less
resources/src/mediawiki.skinning/elements.css
resources/src/mediawiki.skinning/interface.css
resources/src/mediawiki.special/mediawiki.special.css
resources/src/mediawiki.special/mediawiki.special.preferences.styles.css
resources/src/mediawiki.special/mediawiki.special.search.css [changed mode: 0755->0644]
resources/src/mediawiki.special/mediawiki.special.userlogin.signup.css
resources/src/mediawiki.widgets.datetime/CalendarWidget.less
resources/src/mediawiki.widgets.datetime/DateTimeInputWidget.less
resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.less
resources/src/mediawiki.widgets/mw.widgets.StashedFileWidget.less
resources/src/mediawiki/mediawiki.apihelp.css
resources/src/mediawiki/mediawiki.content.json.css
resources/src/mediawiki/mediawiki.debug.less
resources/src/mediawiki/mediawiki.filewarning.less
resources/src/mediawiki/mediawiki.htmlform.css
resources/src/mediawiki/mediawiki.notification.css
resources/src/mediawiki/mediawiki.searchSuggest.css
resources/src/mediawiki/page/gallery.css
tests/browser/features/edit_page.feature
tests/browser/features/view_history.feature
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/HtmlTest.php
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/Services/ServiceContainerTest.php
tests/phpunit/includes/TestUser.php
tests/phpunit/includes/actions/ActionTest.php
tests/phpunit/includes/api/ApiSetNotificationTimestampIntegrationTest.php [new file with mode: 0644]
tests/phpunit/includes/config/ConfigFactoryTest.php
tests/phpunit/includes/debug/MWDebugTest.php

diff --git a/.stylelintrc b/.stylelintrc
new file mode 100644 (file)
index 0000000..e8e1567
--- /dev/null
@@ -0,0 +1,16 @@
+{
+       "rules": {
+               "color-hex-case": [ "lower" ],
+               "color-hex-length": [ "short" ],
+               "color-named": [ "never" ],
+               "color-no-invalid-hex": true,
+
+               "declaration-bang-space-after": [ "never" ],
+               "declaration-bang-space-before": [ "always" ],
+               "declaration-colon-space-after": [ "always" ],
+               "declaration-colon-space-before": [ "never" ],
+
+               "font-family-name-quotes": [ "single-unless-keyword" ],
+               "font-weight-notation": [ "named-where-possible" ]
+       }
+}
index 354f048..a08db5c 100644 (file)
@@ -2,6 +2,7 @@
 module.exports = function ( grunt ) {
        grunt.loadNpmTasks( 'grunt-contrib-copy' );
        grunt.loadNpmTasks( 'grunt-contrib-jshint' );
+       grunt.loadNpmTasks( 'grunt-stylelint' );
        grunt.loadNpmTasks( 'grunt-contrib-watch' );
        grunt.loadNpmTasks( 'grunt-banana-checker' );
        grunt.loadNpmTasks( 'grunt-jscs' );
@@ -39,9 +40,15 @@ module.exports = function ( grunt ) {
                        api: 'includes/api/i18n/',
                        installer: 'includes/installer/i18n/'
                },
+               stylelint: {
+                       options: {
+                               syntax: 'less'
+                       },
+                       src: '{resources/src/*,mw-config/**}/*.{css,less}'
+               },
                watch: {
                        files: [
-                               '.js*',
+                               '.{stylelintrc,jscsrc,jshintignore,jshintrc}',
                                '**/*',
                                '!{docs,extensions,node_modules,skins,vendor}/**'
                        ],
@@ -96,7 +103,7 @@ module.exports = function ( grunt ) {
                return !!( process.env.MW_SERVER && process.env.MW_SCRIPT_PATH );
        } );
 
-       grunt.registerTask( 'lint', [ 'jshint', 'jscs', 'jsonlint', 'banana' ] );
+       grunt.registerTask( 'lint', [ 'jshint', 'jscs', 'jsonlint', 'banana', 'stylelint' ] );
        grunt.registerTask( 'qunit', [ 'assert-mw-env', 'karma:main' ] );
 
        grunt.registerTask( 'test', [ 'lint' ] );
index aeb69fd..4e8259b 100644 (file)
@@ -852,6 +852,7 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Services\\CannotReplaceActiveServiceException' => __DIR__ . '/includes/Services/CannotReplaceActiveServiceException.php',
        'MediaWiki\\Services\\ContainerDisabledException' => __DIR__ . '/includes/Services/ContainerDisabledException.php',
        'MediaWiki\\Services\\DestructibleService' => __DIR__ . '/includes/Services/DestructibleService.php',
+       'MediaWiki\\Services\\SalvageableService' => __DIR__ . '/includes/Services/SalvageableService.php',
        'MediaWiki\\Services\\NoSuchServiceException' => __DIR__ . '/includes/Services/NoSuchServiceException.php',
        'MediaWiki\\Services\\ServiceAlreadyDefinedException' => __DIR__ . '/includes/Services/ServiceAlreadyDefinedException.php',
        'MediaWiki\\Services\\ServiceContainer' => __DIR__ . '/includes/Services/ServiceContainer.php',
index 0b70d16..d6db388 100644 (file)
@@ -4844,7 +4844,7 @@ $wgSessionProviders = [
        MediaWiki\Session\BotPasswordSessionProvider::class => [
                'class' => MediaWiki\Session\BotPasswordSessionProvider::class,
                'args' => [ [
-                       'priority' => 40,
+                       'priority' => 75,
                ] ],
        ],
 ];
index e2dc691..4028aa2 100644 (file)
@@ -11,6 +11,7 @@ use LBFactory;
 use LinkCache;
 use Liuggio\StatsdClient\Factory\StatsdDataFactory;
 use LoadBalancer;
+use MediaWiki\Services\SalvageableService;
 use MediaWiki\Services\ServiceContainer;
 use MWException;
 use ObjectCache;
@@ -90,7 +91,7 @@ class MediaWikiServices extends ServiceContainer {
                        // even if it's just a file name or database credentials to load
                        // configuration from.
                        $bootstrapConfig = new GlobalVarConfig();
-                       self::$instance = self::newInstance( $bootstrapConfig );
+                       self::$instance = self::newInstance( $bootstrapConfig, 'load' );
                }
 
                return self::$instance;
@@ -123,7 +124,7 @@ class MediaWikiServices extends ServiceContainer {
        /**
         * Creates a new instance of MediaWikiServices and sets it as the global default
         * instance. getInstance() will return a different MediaWikiServices object
-        * after every call to resetGlobalServiceLocator().
+        * after every call to resetGlobalInstance().
         *
         * @since 1.28
         *
@@ -131,7 +132,7 @@ class MediaWikiServices extends ServiceContainer {
         * when the configuration has changed significantly since bootstrap time, e.g.
         * during the installation process or during testing.
         *
-        * @warning Calling resetGlobalServiceLocator() may leave the application in an inconsistent
+        * @warning Calling resetGlobalInstance() may leave the application in an inconsistent
         * state. Calling this is only safe under the ASSUMPTION that NO REFERENCE to
         * any of the services managed by MediaWikiServices exist. If any service objects
         * managed by the old MediaWikiServices instance remain in use, they may INTERFERE
@@ -152,11 +153,14 @@ class MediaWikiServices extends ServiceContainer {
         *        was no previous instance, a new GlobalVarConfig object will be used to
         *        bootstrap the services.
         *
+        * @param string $quick Set this to "quick" to allow expensive resources to be re-used.
+        * See SalvageableService for details.
+        *
         * @throws MWException If called after MW_SERVICE_BOOTSTRAP_COMPLETE has been defined in
         *         Setup.php (unless MW_PHPUNIT_TEST or MEDIAWIKI_INSTALL or RUN_MAINTENANCE_IF_MAIN
         *          is defined).
         */
-       public static function resetGlobalInstance( Config $bootstrapConfig = null ) {
+       public static function resetGlobalInstance( Config $bootstrapConfig = null, $quick = '' ) {
                if ( self::$instance === null ) {
                        // no global instance yet, nothing to reset
                        return;
@@ -168,9 +172,38 @@ class MediaWikiServices extends ServiceContainer {
                        $bootstrapConfig = self::$instance->getBootstrapConfig();
                }
 
-               self::$instance->destroy();
+               $oldInstance = self::$instance;
 
                self::$instance = self::newInstance( $bootstrapConfig );
+               self::$instance->importWiring( $oldInstance, [ 'BootstrapConfig' ] );
+
+               if ( $quick === 'quick' ) {
+                       self::$instance->salvage( $oldInstance );
+               } else {
+                       $oldInstance->destroy();
+               }
+
+       }
+
+       /**
+        * Salvages the state of any salvageable service instances in $other.
+        *
+        * @note $other will have been destroyed when salvage() returns.
+        *
+        * @param MediaWikiServices $other
+        */
+       private function salvage( self $other ) {
+               foreach ( $this->getServiceNames() as $name ) {
+                       $oldService = $other->peekService( $name );
+
+                       if ( $oldService instanceof SalvageableService ) {
+                               /** @var SalvageableService $newService */
+                               $newService = $this->getService( $name );
+                               $newService->salvage( $oldService );
+                       }
+               }
+
+               $other->destroy();
        }
 
        /**
@@ -179,21 +212,23 @@ class MediaWikiServices extends ServiceContainer {
         * ServiceWiringFiles setting are loaded, and the MediaWikiServices hook is called.
         *
         * @param Config|null $bootstrapConfig The Config object to be registered as the
-        *        'BootstrapConfig' service. This has to contain at least the information
-        *        needed to set up the 'ConfigFactory' service. If not provided, any call
-        *        to getBootstrapConfig(), getConfigFactory, or getMainConfig will fail.
-        *        A MediaWikiServices instance without access to configuration is called
-        *        "primordial".
+        *        'BootstrapConfig' service.
+        *
+        * @param string $loadWiring set this to 'load' to load the wiring files specified
+        *        in the 'ServiceWiringFiles' setting in $bootstrapConfig.
         *
         * @return MediaWikiServices
         * @throws MWException
+        * @throws \FatalError
         */
-       private static function newInstance( Config $bootstrapConfig ) {
+       private static function newInstance( Config $bootstrapConfig, $loadWiring = '' ) {
                $instance = new self( $bootstrapConfig );
 
                // Load the default wiring from the specified files.
-               $wiringFiles = $bootstrapConfig->get( 'ServiceWiringFiles' );
-               $instance->loadWiringFiles( $wiringFiles );
+               if ( $loadWiring === 'load' ) {
+                       $wiringFiles = $bootstrapConfig->get( 'ServiceWiringFiles' );
+                       $instance->loadWiringFiles( $wiringFiles );
+               }
 
                // Provide a traditional hook point to allow extensions to configure services.
                Hooks::run( 'MediaWikiServices', [ $instance ] );
@@ -265,7 +300,7 @@ class MediaWikiServices extends ServiceContainer {
         * instances to clean up.
         *
         * @param string $name
-        * @param string $destroy Whether the service instance should be destroyed if it exists.
+        * @param bool $destroy Whether the service instance should be destroyed if it exists.
         *        When set to false, any existing service instance will effectively be detached
         *        from the container.
         *
diff --git a/includes/Services/SalvageableService.php b/includes/Services/SalvageableService.php
new file mode 100644 (file)
index 0000000..a613050
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+namespace MediaWiki\Services;
+
+/**
+ * Interface for salvageable services.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @since 1.28
+ */
+
+/**
+ * SalvageableService defines an interface for services that are able to salvage state from a
+ * previous instance of the same class. The intent is to allow new service instances to re-use
+ * resources that would be expensive to re-create, such as cached data or network connections.
+ *
+ * @note There is no expectation that services will be destroyed when the process (or web request)
+ * terminates.
+ */
+interface SalvageableService {
+
+       /**
+        * Re-uses state from $other. $other must not be used after being passed to salvage(),
+        * and should be considered to be destroyed.
+        *
+        * @note Implementations are responsible for determining what parts of $other can be re-used
+        * safely. In particular, implementations should check that the relevant configuration of
+        * $other is the same as in $this before re-using resources from $other.
+        *
+        * @note Implementations must take care to detach any re-used resources from the original
+        * service instance. If $other is destroyed later, resources that are now used by the
+        * new service instance must not be affected.
+        *
+        * @note If $other is a DestructibleService, implementations should make sure that $other
+        * is in destroyed state after salvage finished. This may be done by calling $other->destroy()
+        * after carefully detaching all relevant resources.
+        *
+        * @param SalvageableService $other The object to salvage state from. $other must have the
+        * exact same type as $this.
+        */
+       public function salvage( SalvageableService $other );
+
+}
index 66ee918..b336795 100644 (file)
@@ -55,6 +55,11 @@ class ServiceContainer implements DestructibleService {
         */
        private $serviceInstantiators = [];
 
+       /**
+        * @var boolean[] disabled status, per service name
+        */
+       private $disabled = [];
+
        /**
         * @var array
         */
@@ -126,6 +131,28 @@ class ServiceContainer implements DestructibleService {
                }
        }
 
+       /**
+        * Imports all wiring defined in $container. Wiring defined in $container
+        * will override any wiring already defined locally. However, already
+        * existing service instances will be preserved.
+        *
+        * @since 1.28
+        *
+        * @param ServiceContainer $container
+        * @param string[] $skip A list of service names to skip during import
+        */
+       public function importWiring( ServiceContainer $container, $skip = [] ) {
+               $newInstantiators = array_diff_key(
+                       $container->serviceInstantiators,
+                       array_flip( $skip )
+               );
+
+               $this->serviceInstantiators = array_merge(
+                       $this->serviceInstantiators,
+                       $newInstantiators
+               );
+       }
+
        /**
         * Returns true if a service is defined for $name, that is, if a call to getService( $name )
         * would return a service instance.
@@ -220,6 +247,7 @@ class ServiceContainer implements DestructibleService {
                }
 
                $this->serviceInstantiators[$name] = $instantiator;
+               unset( $this->disabled[$name] );
        }
 
        /**
@@ -244,9 +272,7 @@ class ServiceContainer implements DestructibleService {
        public function disableService( $name ) {
                $this->resetService( $name );
 
-               $this->redefineService( $name, function() use ( $name ) {
-                       throw new ServiceDisabledException( $name );
-               } );
+               $this->disabled[$name] = true;
        }
 
        /**
@@ -282,6 +308,7 @@ class ServiceContainer implements DestructibleService {
                }
 
                unset( $this->services[$name] );
+               unset( $this->disabled[$name] );
        }
 
        /**
@@ -299,7 +326,8 @@ class ServiceContainer implements DestructibleService {
         * @param string $name The service name
         *
         * @throws NoSuchServiceException if $name is not a known service.
-        * @throws ServiceDisabledException if this container has already been destroyed.
+        * @throws ContainerDisabledException if this container has already been destroyed.
+        * @throws ServiceDisabledException if the requested service has been disabled.
         *
         * @return object The service instance
         */
@@ -308,6 +336,10 @@ class ServiceContainer implements DestructibleService {
                        throw new ContainerDisabledException();
                }
 
+               if ( isset( $this->disabled[$name] ) ) {
+                       throw new ServiceDisabledException( $name );
+               }
+
                if ( !isset( $this->services[$name] ) ) {
                        $this->services[$name] = $this->createService( $name );
                }
@@ -327,6 +359,7 @@ class ServiceContainer implements DestructibleService {
                                $this->serviceInstantiators[$name],
                                array_merge( [ $this ], $this->extraInstantiationParams )
                        );
+                       // NOTE: when adding more wiring logic here, make sure copyWiring() is kept in sync!
                } else {
                        throw new NoSuchServiceException( $name );
                }
index e57b96a..5b19b5f 100644 (file)
@@ -517,7 +517,7 @@ if ( !class_exists( 'AutoLoader' ) ) {
 
 // Reset the global service locator, so any services that have already been created will be
 // re-created while taking into account any custom settings and extensions.
-MediaWikiServices::resetGlobalInstance( new GlobalVarConfig() );
+MediaWikiServices::resetGlobalInstance( new GlobalVarConfig(), 'quick' );
 
 // Define a constant that indicates that the bootstrapping of the service locator
 // is complete.
index 4208b3b..84bf16e 100644 (file)
@@ -62,7 +62,7 @@ abstract class Action {
         * the action is disabled, or null if it's not recognised
         * @param string $action
         * @param array $overrides
-        * @return bool|null|string|callable
+        * @return bool|null|string|callable|Action
         */
        final private static function getClass( $action, array $overrides ) {
                global $wgActions;
@@ -147,7 +147,7 @@ abstract class Action {
                // Trying to get a WikiPage for NS_SPECIAL etc. will result
                // in WikiPage::factory throwing "Invalid or virtual namespace -1 given."
                // For SpecialPages et al, default to action=view.
-               if ( $actionName === '' || !$context->canUseWikiPage() ) {
+               if ( !$context->canUseWikiPage() ) {
                        return 'view';
                }
 
index a299e87..f335682 100644 (file)
@@ -142,17 +142,10 @@ class ApiSetNotificationTimestamp extends ApiBase {
                                );
 
                                // Query the results of our update
-                               $timestamps = [];
-                               $lb = new LinkBatch( $pageSet->getTitles() );
-                               $res = $dbw->select(
-                                       'watchlist',
-                                       [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
-                                       [ 'wl_user' => $user->getId(), $lb->constructSet( 'wl', $dbw ) ],
-                                       __METHOD__
+                               $timestamps = $watchedItemStore->getNotificationTimestampsBatch(
+                                       $user,
+                                       $pageSet->getTitles()
                                );
-                               foreach ( $res as $row ) {
-                                       $timestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp;
-                               }
 
                                // Now, put the valid titles into the result
                                /** @var $title Title */
index 4c0d177..f25039a 100644 (file)
@@ -23,7 +23,8 @@
                        "Umherirrender",
                        "Elfix",
                        "Lbayle",
-                       "Verdy p"
+                       "Verdy p",
+                       "Yasten"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentation]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> Toutes les fonctionnalités affichées sur cette page devraient fonctionner, mais l’API est encore en cours de développement et peut changer à tout moment. Inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un en-tête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet en-tête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:API:Errors_and_warnings|API: Errors and warnings]].\n\n<strong>Test :</strong> Pour faciliter le test des requêtes de l’API, voyez [[Special:ApiSandbox]].",
@@ -52,6 +53,8 @@
        "apihelp-block-param-watchuser": "Surveiller les pages utilisateur et de discussion de l’utilisateur ou de l’adresse IP.",
        "apihelp-block-example-ip-simple": "Bloquer l’adresse IP <kbd>192.0.2.5</kbd> pour trois jours avec le motif <kbd>Premier avertissement</kbd>.",
        "apihelp-block-example-user-complex": "Bloquer indéfiniment l’utilisateur <kbd>Vandal</kbd> avec le motif <kbd>Vandalism</kbd>, et empêcher la création de nouveau compte et l'envoi de courriel.",
+       "apihelp-changeauthenticationdata-description": "Modifier les données d’authentification pour l’utilisateur actuel.",
+       "apihelp-changeauthenticationdata-example-password": "Tentative de modification du mot de passe de l’utilisateur actuel en <kbd>ExempleMotDePasse</kbd>.",
        "apihelp-checktoken-description": "Vérifier la validité d'un jeton de <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
        "apihelp-checktoken-param-type": "Type de jeton testé",
        "apihelp-checktoken-param-token": "Jeton à tester.",
@@ -59,6 +62,9 @@
        "apihelp-checktoken-example-simple": "Tester la validité d'un jeton de <kbd>csrf</kbd>.",
        "apihelp-clearhasmsg-description": "Efface le drapeau <code>hasmsg</code> pour l’utilisateur courant.",
        "apihelp-clearhasmsg-example-1": "Effacer le drapeau <code>hasmsg</code> pour l’utilisateur courant",
+       "apihelp-clientlogin-description": "Se connecter au wiki en utilisant le flux interactif.",
+       "apihelp-clientlogin-example-login": "Commencer le processus de connexion au wiki en tant qu’utilisateur <kbd>Exemple</kbd> avec le mot de passe <kbd>ExempleMotDePasse</kbd>.",
+       "apihelp-clientlogin-example-login2": "Continuer la connexion après une réponse de l’IHM pour l’authentification à deux facteurs, en fournissant un <var>OATHToken</var> valant <kbd>987654</kbd>.",
        "apihelp-compare-description": "Obtenir la différence entre 2 pages.\n\nVous devez passer un numéro de révision, un titre de page, ou un ID de page, à la fois pour « from » et « to ».",
        "apihelp-compare-param-fromtitle": "Premier titre à comparer.",
        "apihelp-compare-param-fromid": "ID de la première page à comparer.",
@@ -68,6 +74,7 @@
        "apihelp-compare-param-torev": "Seconde révision à comparer.",
        "apihelp-compare-example-1": "Créer une différence entre les révisions 1 et 2",
        "apihelp-createaccount-description": "Créer un nouveau compte utilisateur.",
+       "apihelp-createaccount-example-create": "Commencer le processus de création d’un utilisateur <kbd>Exemple</kbd> avec le mot de passe <kbd>ExempleMotDePasse</kbd>.",
        "apihelp-createaccount-param-name": "Nom d’utilisateur.",
        "apihelp-createaccount-param-password": "Mot de passe (ignoré si <var>$1mailpassword</var> est défini).",
        "apihelp-createaccount-param-domain": "Domaine pour l’authentification externe (facultatif).",
        "apihelp-import-param-namespace": "Importer vers cet espace de noms. Impossible à utiliser avec <var>$1rootpage</var>.",
        "apihelp-import-param-rootpage": "Importer comme une sous-page de cette page. Impossible à utiliser avec <var>$1namespace</var>.",
        "apihelp-import-example-import": "Importer [[meta:Help:ParserFunctions]] vers l’espace de noms 100 avec tout l’historique.",
-       "apihelp-login-description": "Se connecter et obtenir les cookies d’authentification.\n\nDans le cas d’une connexion réussie, les cookies nécessaires seront inclus dans les entêtes de la réponse HTTP. Dans le cas d’une connexion en échec, les essais ultérieurs pourront être réduits afin de limiter les attaques automatisées de découverte du mot de passe.",
+       "apihelp-linkaccount-description": "Lier un compte d’un fournisseur tiers à l’utilisateur actuel.",
+       "apihelp-linkaccount-example-link": "Commencer le processus de liaison d’un compte depuis <kbd>Exemple</kbd>.",
+       "apihelp-login-description": "Se connecter et obtenir les cookies d’authentification.\n\nCette action ne devrait être utilisée qu’en lien avec [[Special:BotPasswords]] ; l’utiliser pour la connexion du compte principal est obsolète et peut échouer sans avertissement. Pour se connecter sans problème au compte principal, utiliser <kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
+       "apihelp-login-description-nobotpasswords": "Se connecter et obtenir les cookies d’authentification.\n\nCette action est obsolète et peut échouer sans prévenir. Pour se connecter sans problème, utiliser <kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
+       "apihelp-login-description-nonauthmanager": "Se connecter et obtenir les cookies d’authentification.\n\nDans le cas d’une connexion réussie, les cookies nécessaires seront inclus dans les entêtes HTTP de la réponse. Dans le cas d’une connexion en échec, des tentatives ultérieures pourront être limitées pour éviter les attaques automatiques pour deviner les mots de passe.",
        "apihelp-login-param-name": "Nom d’utilisateur.",
        "apihelp-login-param-password": "Mot de passe.",
        "apihelp-login-param-domain": "Domaine (facultatif).",
        "apihelp-query+allusers-param-activeusers": "Lister uniquement les utilisateurs actifs durant {{PLURAL:$1|le dernier jour|les $1 derniers jours}}.",
        "apihelp-query+allusers-param-attachedwiki": "Avec <kbd>$1prop=centralids</kbd>, indiquer aussi si l’utilisateur est attaché avec le wiki identifié par cet ID.",
        "apihelp-query+allusers-example-Y": "Lister les utilisateurs en commençant à <kbd>Y</kbd>.",
+       "apihelp-query+authmanagerinfo-description": "Récupérer les informations concernant l’état d’authentification actuel.",
+       "apihelp-query+authmanagerinfo-param-securitysensitiveoperation": "Tester si l’état d’authentification actuel de l’utilisateur est suffisant pour l’opération spécifiée comme sensible du point de vue sécurité.",
+       "apihelp-query+authmanagerinfo-param-requestsfor": "Récupérer les informations sur les requêtes d’authentification nécessaires pour l’action d’authentification spécifiée.",
+       "apihelp-query+filerepoinfo-example-login": "Récupérer les requêtes qui peuvent être utilisées en commençant une connexion.",
+       "apihelp-query+filerepoinfo-example-login-merged": "Récupérer les requêtes qui peuvent être utilisées au début de la connexion, avec les champs de formulaire intégrés.",
+       "apihelp-query+filerepoinfo-example-securitysensitiveoperation": "Tester si l’authentification est suffisante pour l’action <kbd>foo</kbd>.",
        "apihelp-query+backlinks-description": "Trouver toutes les pages qui ont un lien vers la page donnée.",
        "apihelp-query+backlinks-param-title": "Titre à rechercher. Impossible à utiliser avec <var>$1pageid</var>.",
        "apihelp-query+backlinks-param-pageid": "ID de la page à chercher. Impossible à utiliser avec <var>$1title</var>.",
        "apihelp-query+siteinfo-paramvalue-prop-variables": "Renvoie une liste des IDs de variable.",
        "apihelp-query+siteinfo-paramvalue-prop-protocols": "Renvoie une liste des protocoles qui sont autorisés dans les liens externes.",
        "apihelp-query+siteinfo-paramvalue-prop-defaultoptions": "Renvoie les valeurs par défaut pour les préférences utilisateur.",
+       "apihelp-query+siteinfo-paramvalue-prop-uploaddialog": "Renvoie la configuration du dialogue de téléversement.",
        "apihelp-query+siteinfo-param-filteriw": "Renvoyer uniquement les entrées locales ou uniquement les non locales de la correspondance interwiki.",
        "apihelp-query+siteinfo-param-showalldb": "Lister tous les serveurs de base de données, pas seulement celui avec la plus grande latence.",
        "apihelp-query+siteinfo-param-numberingroup": "Liste le nombre d’utilisateurs dans les groupes.",
        "apihelp-query+users-paramvalue-prop-emailable": "Marque si l’utilisateur peut et veut recevoir des courriels via [[Special:Emailuser]].",
        "apihelp-query+users-paramvalue-prop-gender": "Marque le sexe de l’utilisateur. Renvoie « male », « female », ou « unknown ».",
        "apihelp-query+users-paramvalue-prop-centralids": "Ajoute les IDs centraux et l’état d’attachement de l’utilisateur.",
+       "apihelp-query+users-paramvalue-prop-cancreate": "Indique si un compte peut être créé pour les noms d’utilisateurs valides mais non enregistrés.",
        "apihelp-query+users-param-attachedwiki": "Avec <kbd>$1prop=centralids</kbd>, indiquer si l’utilisateur est attaché au wiki identifié par cet ID.",
        "apihelp-query+users-param-users": "Une liste des utilisateurs sur lesquels obtenir de l’information.",
        "apihelp-query+users-param-token": "Utiliser plutôt <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
        "apihelp-query+watchlistraw-param-totitle": "Terminer l'énumération avec ce Titre (inclure le préfixe d'espace de noms) :",
        "apihelp-query+watchlistraw-example-simple": "Lister les pages dans la liste de suivi de l’utilisateur actuel",
        "apihelp-query+watchlistraw-example-generator": "Chercher l’information sur les pages de la liste de suivi de l’utilisateur actuel",
+       "apihelp-removeauthenticationdata-description": "Supprimer les données d’authentification pour l’utilisateur actuel.",
+       "apihelp-removeauthenticationdata-example-simple": "Tentative de suppression des données de l’utilisateur pour <kbd>FooAuthenticationRequest</kbd>.",
+       "apihelp-resetpassword-description": "Envoyer un courriel de réinitialisation du mot de passe à un utilisateur.",
+       "apihelp-resetpassword-description-noroutes": "Aucun chemin pour réinitialiser le mot de passe n’est disponible.\n\nActiver les chemins dans <var>[[mw:Manual:$wgPasswordResetRoutes|$wgPasswordResetRoutes]]</var> pour utiliser ce module.",
+       "apihelp-resetpassword-param-user": "Utilisateur ayant été réinitialisé.",
+       "apihelp-resetpassword-param-email": "Adresse courriel de l’utilisateur ayant été réinitialisé.",
+       "apihelp-resetpassword-example-email": "Envoyer un courriel pour la réinitialisation de mot de passe à tous les utilisateurs avec une adresse email <kbd>user@example.com</kbd>.",
        "apihelp-revisiondelete-description": "Supprimer et annuler la suppression des révisions.",
        "apihelp-revisiondelete-param-type": "Type de suppression de révision en cours de traitement.",
        "apihelp-revisiondelete-param-target": "Titre de page pour la suppression de révision, s’il est nécessaire pour le type.",
index a9b9099..c82512a 100644 (file)
        "apihelp-query+watchlist-paramvalue-prop-comment": "Dodaje komentarz do edycji.",
        "apihelp-query+watchlist-paramvalue-prop-timestamp": "Dodaje znacznik czasu edycji.",
        "apihelp-query+watchlist-paramvalue-prop-sizes": "Dodaje starą i nową długość strony.",
+       "apihelp-resetpassword-description": "Wyślij użytkownikowi e-mail do resetowania hasła.",
        "apihelp-stashedit-param-title": "Tytuł edytowanej strony.",
        "apihelp-stashedit-param-sectiontitle": "Tytuł nowej sekcji.",
        "apihelp-stashedit-param-text": "Zawartość strony.",
index 71daba7..810062f 100644 (file)
@@ -47,6 +47,7 @@
        "apihelp-block-param-watchuser": "监视用户或该 IP 的用户页和讨论页。",
        "apihelp-block-example-ip-simple": "封禁IP地址<kbd>192.0.2.5</kbd>三天,原因<kbd>First strike</kbd>。",
        "apihelp-block-example-user-complex": "无限期封禁用户<kbd>Vandal</kbd>,原因<kbd>Vandalism</kbd>,并阻止新账户创建和电子邮件发送。",
+       "apihelp-changeauthenticationdata-description": "更改当前用户的身份验证数据。",
        "apihelp-checktoken-description": "从<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>检查令牌有效性。",
        "apihelp-checktoken-param-type": "已开始测试的令牌类型。",
        "apihelp-checktoken-param-token": "要测试的令牌。",
@@ -54,6 +55,7 @@
        "apihelp-checktoken-example-simple": "测试<kbd>csrf</kbd>令牌的有效性。",
        "apihelp-clearhasmsg-description": "清除当前用户的<code>hasmsg</code>标记。",
        "apihelp-clearhasmsg-example-1": "清除当前用户的<code>hasmsg</code>标记。",
+       "apihelp-clientlogin-description": "使用交互式流登录wiki。",
        "apihelp-compare-description": "获取2个页面之间的差别。\n\n用于“from”和“to”的修订版本号、页面标题或页面 ID 必须获得通过。",
        "apihelp-compare-param-fromtitle": "要比较的第一个标题。",
        "apihelp-compare-param-fromid": "要比较的第一个页面 ID。",
@@ -63,6 +65,7 @@
        "apihelp-compare-param-torev": "要比较的第二个修订版本。",
        "apihelp-compare-example-1": "在版本1和2中创建差异。",
        "apihelp-createaccount-description": "创建一个新用户账户。",
+       "apihelp-createaccount-example-create": "开始创建用户<kbd>Example</kbd>和密码<kbd>ExamplePassword</kbd>的流程。",
        "apihelp-createaccount-param-name": "用户名。",
        "apihelp-createaccount-param-password": "密码(如果设置<var>$1mailpassword</var>则忽略)。",
        "apihelp-createaccount-param-domain": "外部身份验证域 (可选)。",
        "apihelp-import-param-namespace": "导入至此名字空间。不能与<var>$1rootpage</var>一起使用。",
        "apihelp-import-param-rootpage": "作为此页面的子页面导入。不能与<var>$1namespace</var>一起使用。",
        "apihelp-import-example-import": "将页面[[meta:Help:ParserFunctions]]连带完整历史导入至100名字空间。",
+       "apihelp-linkaccount-description": "将来自第三方提供商的账户链接至当前用户。",
        "apihelp-login-description": "登录并获得身份验证Cookie。\n\n在成功登录的情况下,所需的Cookie将包含在HTTP响应头中。在登录失败的情况下,进一步的尝试可能会被自动密码猜解攻击的限制所遏制。",
+       "apihelp-login-description-nonauthmanager": "登录并获得身份验证Cookie。\n\n在成功登录的情况下,所需的Cookie将包含在HTTP响应头中。在登录失败的情况下,进一步的尝试可能会被自动密码猜解攻击的限制所遏制。",
        "apihelp-login-param-name": "用户名。",
        "apihelp-login-param-password": "密码。",
        "apihelp-login-param-domain": "域名(可选)。",
        "apihelp-query+watchlistraw-param-totitle": "要列举的最终标题(带名字空间前缀)。",
        "apihelp-query+watchlistraw-example-simple": "列出当前用户的监视列表中的页面。",
        "apihelp-query+watchlistraw-example-generator": "检索当前用户监视列表上的页面的页面信息。",
+       "apihelp-resetpassword-description": "向用户发送密码重置邮件。",
+       "apihelp-resetpassword-param-user": "正在重置的用户。",
+       "apihelp-resetpassword-param-email": "正在重置用户的电子邮件地址。",
+       "apihelp-resetpassword-param-capture": "返回已发送的临时密码。需要<code>passwordreset</code>用户权限。",
+       "apihelp-resetpassword-example-user": "向用户<kbd>Example</kbd>发送密码重置邮件。",
+       "apihelp-resetpassword-example-email": "向所有电子邮件地址为<kbd>user@example.com</kbd>的用户发送密码重置邮件。",
        "apihelp-revisiondelete-description": "删除和恢复修订版本。",
        "apihelp-revisiondelete-param-type": "正在执行的修订版本删除类型。",
        "apihelp-revisiondelete-param-target": "要进行修订版本删除的页面标题,如果对某一类型需要。",
        "api-help-permissions-granted-to": "{{PLURAL:$1|授予}}:$2",
        "api-help-right-apihighlimits": "在API查询中使用更高的上限(慢查询:$1;快查询:$2)。慢查询的限制也适用于多值参数。",
        "api-help-open-in-apisandbox": "<small>[在沙盒中打开]</small>",
+       "api-help-authmanagerhelper-messageformat": "返回消息使用的格式。",
        "api-credits-header": "制作人员",
        "api-credits": "API 开发人员:\n* Yuri Astrakhan(创建者,2006年9月~2007年9月的开发组领导)\n* Roan Kattouw(2007年9月~2009年的开发组领导)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Brad Jorsch(2013年至今的开发组领导)\n\n请将您的评论、建议和问题发送至mediawiki-api@lists.wikimedia.org,或提交错误请求至https://phabricator.wikimedia.org/。"
 }
index 09b0baa..cd25352 100644 (file)
  *
  * @file
  */
+use MediaWiki\Services\SalvageableService;
+use Wikimedia\Assert\Assert;
 
 /**
  * Factory class to create Config objects
  *
  * @since 1.23
  */
-class ConfigFactory {
+class ConfigFactory implements SalvageableService {
 
        /**
         * Map of config name => callback
@@ -50,6 +52,41 @@ class ConfigFactory {
                return \MediaWiki\MediaWikiServices::getInstance()->getConfigFactory();
        }
 
+       /**
+        * Re-uses existing Cache objects from $other. Cache objects are only re-used if the
+        * registered factory function for both is the same. Cache config is not copied,
+        * and only instances of caches defined on this instance with the same config
+        * are copied.
+        *
+        * @see SalvageableService::salvage()
+        *
+        * @param SalvageableService $other The object to salvage state from. $other must have the
+        * exact same type as $this.
+        */
+       public function salvage( SalvageableService $other ) {
+               Assert::parameterType( self::class, $other, '$other' );
+
+               /** @var ConfigFactory $other */
+               foreach ( $other->factoryFunctions as $name => $otherFunc ) {
+                       if ( !isset( $this->factoryFunctions[$name] ) ) {
+                               continue;
+                       }
+
+                       // if the callback function is the same, salvage the Cache object
+                       // XXX: Closures are never equal!
+                       if ( isset( $other->configs[$name] )
+                               && $this->factoryFunctions[$name] == $otherFunc
+                       ) {
+                               $this->configs[$name] = $other->configs[$name];
+                               unset( $other->configs[$name] );
+                       }
+               }
+
+               // disable $other
+               $other->factoryFunctions = [];
+               $other->configs = [];
+       }
+
        /**
         * @return string[]
         */
@@ -67,23 +104,11 @@ class ConfigFactory {
         * @throws InvalidArgumentException If an invalid callback is provided
         */
        public function register( $name, $callback ) {
-               if ( $callback instanceof Config ) {
-                       $instance = $callback;
-
-                       // Register a callback anyway, for consistency. Note that getConfigNames()
-                       // relies on $factoryFunctions to have all config names.
-                       $callback = function() use ( $instance ) {
-                               return $instance;
-                       };
-               } else {
-                       $instance = null;
-               }
-
-               if ( !is_callable( $callback ) ) {
+               if ( !is_callable( $callback ) && !( $callback instanceof Config ) ) {
                        throw new InvalidArgumentException( 'Invalid callback provided' );
                }
 
-               $this->configs[$name] = $instance;
+               unset( $this->configs[$name] );
                $this->factoryFunctions[$name] = $callback;
        }
 
@@ -105,7 +130,13 @@ class ConfigFactory {
                        if ( !isset( $this->factoryFunctions[$key] ) ) {
                                throw new ConfigException( "No registered builder available for $name." );
                        }
-                       $conf = call_user_func( $this->factoryFunctions[$key], $this );
+
+                       if ( $this->factoryFunctions[$key] instanceof Config ) {
+                               $conf = $this->factoryFunctions[$key];
+                       } else {
+                               $conf = call_user_func( $this->factoryFunctions[$key], $this );
+                       }
+
                        if ( $conf instanceof Config ) {
                                $this->configs[$name] = $conf;
                        } else {
index f3d6781..e225fb7 100644 (file)
@@ -641,7 +641,12 @@ abstract class ContentHandler {
         *
         * @since 1.21
         *
-        * @return array Always an empty array.
+        * @return array An array mapping action names (typically "view", "edit", "history" etc.) to
+        *  either the full qualified class name of an Action class, a callable taking ( Page $page,
+        *  IContextSource $context = null ) as parameters and returning an Action object, or an actual
+        *  Action object. An empty array in this default implementation.
+        *
+        * @see Action::factory
         */
        public function getActionOverrides() {
                return [];
index ac08374..1f7f3b0 100644 (file)
@@ -389,14 +389,14 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
                foreach ( $deleteWheres as $deleteWhere ) {
                        $this->mDb->delete( $table, $deleteWhere, __METHOD__ );
                        $this->mDb->commit( __METHOD__, 'flush' );
-                       wfGetLBFactory()->waitForReplication();
+                       wfGetLBFactory()->waitForReplication( [ 'wiki' => $this->mDb->getWikiID() ] );
                }
 
                $insertBatches = array_chunk( $insertions, self::BATCH_SIZE );
                foreach ( $insertBatches as $insertBatch ) {
                        $this->mDb->insert( $table, $insertBatch, __METHOD__, 'IGNORE' );
                        $this->mDb->commit( __METHOD__, 'flush' );
-                       wfGetLBFactory()->waitForReplication();
+                       wfGetLBFactory()->waitForReplication( [ 'wiki' => $this->mDb->getWikiID() ] );
                }
 
                if ( count( $insertions ) ) {
index 0dab3bb..e891c9c 100644 (file)
@@ -1226,17 +1226,25 @@ class HTMLForm extends ContextSource {
        /**
         * Identify that the submit button in the form has a destructive action
         * @since 1.24
+        *
+        * @return HTMLForm $this for chaining calls (since 1.28)
         */
        public function setSubmitDestructive() {
                $this->mSubmitFlags = [ 'destructive', 'primary' ];
+
+               return $this;
        }
 
        /**
         * Identify that the submit button in the form has a progressive action
         * @since 1.25
+        *
+        * @return HTMLForm $this for chaining calls (since 1.28)
         */
        public function setSubmitProgressive() {
                $this->mSubmitFlags = [ 'progressive', 'primary' ];
+
+               return $this;
        }
 
        /**
index 6c42e34..4c9eaed 100644 (file)
@@ -2204,7 +2204,7 @@ class Article implements Page {
 
        /**
         * Call to WikiPage function for backwards compatibility.
-        * @see WikiPage::getActionOverrides
+        * @see ContentHandler::getActionOverrides
         */
        public function getActionOverrides() {
                return $this->mPage->getActionOverrides();
index 040a6f3..8702156 100644 (file)
@@ -183,18 +183,12 @@ class WikiPage implements Page, IDBAccessObject {
        }
 
        /**
-        * Returns overrides for action handlers.
-        * Classes listed here will be used instead of the default one when
-        * (and only when) $wgActions[$action] === true. This allows subclasses
-        * to override the default behavior.
-        *
         * @todo Move this UI stuff somewhere else
         *
-        * @return array
+        * @see ContentHandler::getActionOverrides
         */
        public function getActionOverrides() {
-               $content_handler = $this->getContentHandler();
-               return $content_handler->getActionOverrides();
+               return $this->getContentHandler()->getActionOverrides();
        }
 
        /**
index c6d3f6e..5d230c0 100644 (file)
@@ -872,7 +872,7 @@ class SpecialUndelete extends SpecialPage {
                        "ids" => $revisions,
                        "target" => $this->mTargetObj->getPrefixedText()
                ];
-               $url = SpecialPage::getTitleFor( "RevisionDelete" )->getFullURL( $query );
+               $url = SpecialPage::getTitleFor( 'Revisiondelete' )->getFullURL( $query );
                $this->getOutput()->redirect( $url );
        }
 
index 71023c0..ce2ac83 100644 (file)
@@ -3859,6 +3859,7 @@ class User implements IDBAccessObject {
                if ( !$session->canSetUser() ) {
                        \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
                                ->warning( __METHOD__ . ": Cannot log out of an immutable session" );
+                       $error = 'immutable';
                } elseif ( !$session->getUser()->equals( $this ) ) {
                        \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
                                ->warning( __METHOD__ .
@@ -3866,6 +3867,7 @@ class User implements IDBAccessObject {
                                );
                        // But we still may as well make this user object anon
                        $this->clearInstanceCache( 'defaults' );
+                       $error = 'wronguser';
                } else {
                        $this->clearInstanceCache( 'defaults' );
                        $delay = $session->delaySave();
@@ -3874,7 +3876,13 @@ class User implements IDBAccessObject {
                        $session->setUser( new User );
                        $session->set( 'wsUserID', 0 ); // Other code expects this
                        ScopedCallback::consume( $delay );
+                       $error = false;
                }
+               \MediaWiki\Logger\LoggerFactory::getInstance( 'authmanager' )->info( 'Logout', [
+                       'event' => 'logout',
+                       'successful' => $error === false,
+                       'status' => $error ?: 'success',
+               ] );
        }
 
        /**
index 9755531..afb78ab 100644 (file)
        "log-action-filter-protect-unprotect": "رفع الحماية",
        "log-action-filter-rights-rights": "تغيير يدوي",
        "log-action-filter-upload-upload": "رفع جديد",
-       "log-action-filter-upload-overwrite": "إعادة الرفع"
+       "log-action-filter-upload-overwrite": "إعادة الرفع",
+       "authmanager-email-help": "عنوان البريد الإلكتروني",
+       "linkaccounts": "ربط الحسابات",
+       "unlinkaccounts": "إزالة ربط الحسابات"
 }
index 4cfb425..a50f7d8 100644 (file)
        "createacct-reason-ph": "Зь якой мэтай вы ствараеце іншы рахунак",
        "createacct-submit": "Стварыць рахунак",
        "createacct-another-submit": "Стварыць рахунак",
+       "createacct-continue-submit": "Працягнуць стварэньне рахунку",
        "createacct-benefit-heading": "{{SITENAME}} створаная людзьмі, такімі як вы.",
        "createacct-benefit-body1": "{{PLURAL:$1|праўка|праўкі|правак}}",
        "createacct-benefit-body2": "{{PLURAL:$1|старонка|старонкі|старонак}}",
index fceab4d..433f4c0 100644 (file)
        "createacct-reason-ph": "Чаму вы ствараеце іншы ўліковы запіс",
        "createacct-submit": "Стварыць уліковы запіс",
        "createacct-another-submit": "Стварыць уліковы запіс",
+       "createacct-continue-submit": "Працягнуць стварэнне ўліковага запісу",
        "createacct-benefit-heading": "{{SITENAME}} зроблены такімі ж людзьмі, як вы.",
        "createacct-benefit-body1": "{{PLURAL:$1|праўка|праўкі|правак}}",
        "createacct-benefit-body2": "{{PLURAL:$1|старонка|старонкі|старонак}}",
        "createacct-another-realname-tip": "Сапраўднае імя паведамляць неабавязкова.\nКалі вы паведаміце яго, яно будзе выкарыстоўвацца для пазначэння вашага ўкладу.",
        "pt-login": "Увайсці",
        "pt-login-button": "Увайсці",
+       "pt-login-continue-button": "Працягнуць уваход",
        "pt-createaccount": "Стварыць уліковы запіс",
        "pt-userlogout": "Выйсці",
        "php-mail-error-unknown": "Невядомая памылка ў функцыі PHP-пошты",
        "botpasswords-invalid-name": "Паказанае імя ўдзельніка не ўтрымлівае падзяляльнік паролю робата (\"$1\").",
        "botpasswords-not-exist": "Удзельнік \"$1\" не мае паролю для робата з назвай \"$2\".",
        "resetpass_forbidden": "Не дазволена мяняць паролі",
+       "resetpass_forbidden-reason": "Не дазволена мяняць паролі: $1",
        "resetpass-no-info": "Трэба ўвайсці ў сістэму, каб звяртацца да гэтай старонкі наўпрост.",
        "resetpass-submit-loggedin": "Змяніць пароль",
        "resetpass-submit-cancel": "Нічога",
        "passwordreset-emailsentusername": "Калі ёсць адрас электроннай пошты, злучаны з гэтым імем удзельніка, то будзе дасланы ліст пра скід пароля.",
        "passwordreset-emailsent-capture": "Ніжэй прыведзены адпраўлены ліст пра скід пароля.",
        "passwordreset-emailerror-capture": "Ніжэй прыведзены створаны ліст пра скід пароля, яго адпраўка не атрымалася па прычыне: $1",
+       "passwordreset-invalideamil": "Няслушны адрас электроннай пошты",
        "changeemail": "Змяніць або выдаліць адрас электроннай пошты",
        "changeemail-header": "Запоўніце гэтую форму, каб змяніць свой адрас электроннай пошты. Калі хочаце выдаліць адрас электроннай пошты, злучаны з вашым уліковым запісам, пакіньце поле новага адраса электроннай пошты пустым пры адпраўцы формы.",
        "changeemail-passwordrequired": "Вам трэба будзе ўвесці свой пароль, каб пацвердзіць гэта змяненне.",
        "accmailtext": "На адрас $2 быў дасланы згенераваны пароль для [[User talk:$1|$1]]. Ён можа быць зменены на <em>[[Special:ChangePassword|старонцы змены пароля]]</em> пасля ўваходу ў сістэму.",
        "newarticle": "(Новы)",
        "newarticletext": "Вы перайшлі па спасылцы на старонку, якой яшчэ няма.\nКаб яе стварыць, набярыце яе тэкст у ніжэйпаказаным акне рэдагавання (падрабязнасці гл. ў [$1 даведцы]).\nКалі вы тут выпадкова, проста націсніце <strong>назад</strong> у браўзеры.",
-       "anontalkpagetext": "----''Гэта старонка размовы з ананімным удзельнікам, які або не мае свайго рахунка, або ім не карыстаўся. Таму дзеля яго ці яе ідэнтыфікацыі мы мусім выкарыстаць лічбавы IP-адрас. Такі адрас IP могуць дзяліць між сабою некалькі асоб. Калі вы ананімны ўдзельнік, і лічыце, што атрымліваеце няслушныя заўвагі,[[Special:CreateAccount|стварыце рахунак]] або [[Special:UserLogin|зайдзіце ў сістэму]], каб вас больш не блыталі з іншымі ананімнымі ўдзельнікамі.''",
+       "anontalkpagetext": "----\n<em>Гэта старонка размовы з ананімным удзельнікам, які або не мае свайго рахунка, або ім не карыстаўся.</em>\nТаму дзеля яго ці яе ідэнтыфікацыі мы мусім выкарыстаць лічбавы IP-адрас.\nТакі адрас IP могуць дзяліць між сабою некалькі асоб.\nКалі вы ананімны ўдзельнік, і лічыце, што атрымліваеце няслушныя заўвагі,[[Special:CreateAccount|стварыце рахунак]] або [[Special:UserLogin|зайдзіце ў сістэму]], каб вас больш не блыталі з іншымі ананімнымі ўдзельнікамі.",
        "noarticletext": "Старонка не ўтрымлівае тэксту. Вы можаце [[Special:Search/{{PAGENAME}}|пашукаць гэткую назву]] ў іншых старонках ці <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ў журналах],\nабо [{{fullurl:{{FULLPAGENAME}}|action=edit}} папрацаваць з гэтай старонкай]</span>.",
        "noarticletext-nopermission": "Старонка не ўтрымлівае тэксту.\nВы можаце [[Special:Search/{{PAGENAME}}|пашукаць гэткую назву]] ў іншых старонках,\nці <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ў журналах]</span>, але вы не маеце дазволу на стварэнне гэтай старонкі.",
        "missing-revision": "Няма версіі #$1 у старонкі з назвай \"{{FULLPAGENAME}}\".\n\nЗвычайна такое здараецца, калі прайсці па састарэлай спасылцы з гісторыі на старонку, якая была сцёрта.\nПадрабязнасці можна пабачыць у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале сціранняў].",
        "right-managechangetags": "Ствараць і выдаляць [[Special:Tags|біркі]] з базы даных",
        "right-applychangetags": "Прымяняць [[Special:Tags|біркі]] са сваімі праўкамі",
        "right-changetags": "Дадаваць і выдаляць адвольныя [[Special:Tags|біркі]] да асобных версій і запісаў у журнале падзей",
+       "right-deletechangetags": "Выдаляць [[Special:Tags|біркі]] з базы даных",
        "grant-generic": "Набор дазволаў \"$1\"",
        "grant-group-page-interaction": "Узаемадзейнічаць з старонкамі",
        "grant-group-file-interaction": "Узаемадзейнічаць з медыяфайламі",
        "action-viewmyprivateinfo": "бачыць свае асабістыя звесткі",
        "action-editmyprivateinfo": "правіць свае асабістыя звесткі",
        "action-editcontentmodel": "правіць мадэль змесціва старонкі",
-       "action-managechangetags": "ствараць і выдаляць біркі з базы даных",
+       "action-managechangetags": "ствараць і (дэ)актываваць біркі",
        "action-applychangetags": "прымяняць біркі з сваімі праўкамі",
        "action-changetags": "дадаваць і выдаляць адвольныя біркі да асобных версій і запісаў у журнале падзей",
+       "action-deletechangetags": "выдаляць біркі з базы даных",
        "nchanges": "$1 {{PLURAL:$1|змена|змены|змен}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|з часу апошняга наведвання}}",
        "enhancedrc-history": "гісторыя",
index 9b82777..edaf14b 100644 (file)
        "logentry-delete-delete": "$1 ، $3 تاکدیما {{GENDER:$2|پاک کورت}}",
        "logentry-delete-restore": "$1 ، $3 ئی تاکدیما {{GENDER:$2|پدا جۆڑ کورت}}",
        "logentry-delete-event": "$1 پیدایی {{PLURAL:$5|یک مورد سیاه چال|$5 مورد سیاه چال}} ئا بئ $3 {{GENDER:$2|تا تغیر دات}}: $4",
-       "logentry-delete-revision": "$1 پیدایی {{PLURAL:$5|یک نخسه|$5 نخسه}} تاکدیم $3 ئا {{GENDER:$2|تغییر دات}}: $4",
+       "logentry-delete-revision": "$1 ،  $3 تاکدیمئ  {{PLURAL:$5|یک نخسه|$5 نخسه}}‌ئی پیدایي‌ئا   {{GENDER:$2|تغییر دات}}: $4",
        "logentry-suppress-delete": "$1 $3 ئی تاکدیما {{GENDER:$2| سرکوب کورت}}",
        "logentry-suppress-event": "$1 پیدایی {{PLURAL:$5|یک مورد سیاه چال|$5 مورد سیاه چال}} ئا بئ $3 {{GENDER:$2|تا چیهر دات}}: $4",
-       "logentry-suppress-revision": "$1 پیدایی {{PLURAL:$5|یک نخسه|$5 نخسه}} تاکدیم $3 ئا چیهراکائی {{GENDER:$2|تغییر دات}}: $4",
+       "logentry-suppress-revision": "$1 ،  $3 تاکدیمئ  {{PLURAL:$5|یک نخسه|$5 نخسه}}‌ئی پیدایي‌ئا  چیهراکائی  {{GENDER:$2|تغییر دات}}: $4",
        "revdelete-content-hid": "محتوائانه چیهر کورت",
        "revdelete-summary-hid": "ایڈیٹ ئی خلاصه ئا چیهر کورت",
        "revdelete-uname-hid": "چیهرین کار زوروکئ نام",
index 41c9e92..8eb1341 100644 (file)
        "nocookiesnew": "La uzantokonto estis kreita sed vi ne estas ensalutinta. {{SITENAME}} uzas kuketojn por akcepti uzantojn. Kuketoj esta malaktivigitaj ĉe vi. Bonvolu aktivigi ilin kaj ensalutu per viaj novaj salutnomo kaj pasvorto.",
        "nocookieslogin": "{{SITENAME}} uzas kuketojn por akcepti uzantojn. Kuketoj esta malaktivigitaj ĉe vi. Bonvolu aktivigi ilin kaj provu denove.",
        "nocookiesfornew": "La uzantokonto ne estis kreita, ĉar ne konfirmeblas ĝia fonto. Certiginte ke kuketoj estas ebligitaj, reŝargu tiun ĉi paĝon kaj reprovu.",
+       "createacct-loginerror": "La konto estis sukcese kreita sed vi ne povus esti ensalutita aŭtomate. Bonvolu procedi [[Special:UserLogin|malaŭtomatan ensaluton]].",
        "noname": "Vi ne tajpis validan salutnomon.",
        "loginsuccesstitle": "Ensalutis",
        "loginsuccess": "Vi ensalutis ĉe {{SITENAME}} kiel uzanto \"$1\".",
        "passwordreset-emailsentusername": "Se estas retpoŝta adreso, kiu estas asociita kun tiu uzantnomo, tiam ni sendos retpoŝtan mesaĝon pri reagordado de la pasvorto.",
        "passwordreset-emailsent-capture": "Retpoŝto kun renovigita pasvorto estis sendita, kiu estas montrata malsupre.",
        "passwordreset-emailerror-capture": "Retpoŝto kun renovigita pasvorto estis generita, montrata sube, sed sendado al la {{GENDER:$2|uzanto}} malsukcesis: $1",
+       "passwordreset-emailsent-capture2": "La {{PLURAL:$1|retpoŝto|retpoŝtojn}} de pasvorta reensignado estis sendita. La {{PLURAL:$1|salutnomo kaj pasvorto|listo de salutnomoj kaj pasvortoj}} estas vidigita sube.",
+       "passwordreset-emailerror-capture2": "Retpoŝtado al la {{GENDER:$2|uzantiĉo|uzantino|uzanto}} malsukcesis: $1 La {{PLURAL:$3|salutnomo kaj pasvorta|listo de salutnomoj kaj pasvortoj}} estas vidigita sube.",
        "passwordreset-nocaller": "Vokanto devas esti provizita",
        "passwordreset-nosuchcaller": "Vokanto ne ekzistas: $1",
+       "passwordreset-ignored": "La pasvorta reensignado ne estis pritraktita. Eble neniu provizanto estis formita?",
        "passwordreset-invalideamil": "Nevalida retpoŝta adreso",
+       "passwordreset-nodata": "Nek salutnomo nek retpoŝta adreso estis provizita",
        "changeemail": "Ŝanĝi aŭ forigi retpoŝtadreson",
        "changeemail-header": "Plenigu ĉi tiun formularon por ŝanĝi vian retpoŝtadreson. Se vi volas forigi la difinon de retpoŝtadreso por via uzantokonto, lasu la kampon por la nova retpoŝtadreso malplena ĉe la transigo.",
        "changeemail-passwordrequired": "Vi devas entajpi vian pasvorton, por konfirmi ĉi tiun ŝanĝon.",
        "htmlform-cloner-create": "Aldoni plian",
        "htmlform-cloner-delete": "Forigi",
        "htmlform-cloner-required": "Almenaŭ unu valoro estas nepra.",
+       "htmlform-title-badnamespace": "[[:$1]] ne  estas en \"{{ns:$2}}\" nomspaco.",
+       "htmlform-title-not-creatable": "\"$1\" estas nekreebla titolo por paĝo",
        "htmlform-title-not-exists": "$1 ne ekzistas.",
        "htmlform-user-not-exists": "<strong>$1</strong> ne ekzistas.",
        "htmlform-user-not-valid": "<strong>$1</strong> ne estas valida salutnomo.",
        "pagelang-language": "Lingvo",
        "pagelang-use-default": "Uzi defaŭltan lingvon",
        "pagelang-select-lang": "Elekti la lingvon",
+       "pagelang-submit": "Ek!",
        "right-pagelang": "Ŝanĝi paĝan lingvon",
        "action-pagelang": "ŝanĝi la lingvon de la paĝo",
-       "log-name-pagelang": "Ŝanĝi la lingvan protokolon",
+       "log-name-pagelang": "Protokolo pri lingvajn ŝanĝojn",
        "log-description-pagelang": "Jen protokolo pri ŝanĝoj de paĝaj lingvoj.",
        "logentry-pagelang-pagelang": "$1 {{GENDER:$2|ŝanĝis}} la paĝan lingvon por $3 de $4 al $5.",
        "default-skin-not-found": "Ups! La defaŭlta etoso por via vikio, difinita en <code dir=\"ltr\">$wgDefaultSkin</code> kiel <code>$1</code> ne estas disponebla.\n\nŜajnas, ke via instalaĵo enhavas {{PLURAL:$4|jenan etoson|jenajn etosojn}}. Vidu [https://www.mediawiki.org/wiki/Manual:Skin_configuration Manlibro:Agordado de etosoj] por informoj kiel {{PLURAL:$4|ĝin ŝalti|ilin ŝalti kaj elekti la defaŭltan}}.\n\n$2\n\n; Se vi ĵus instalis MediaWiki:\n: Vi probable instalis de git aŭ rekte de fontokodo per alia metodo. Tio estas antaŭsupozata. MediaWiki 1.24 kaj pli novaj versioj enhavas neniun etoson en la ĉefa deponejo. Provu instali iujn etosojn de [https://www.mediawiki.org/wiki/Category:All_skins etosa dosierujo en mediawiki.org] per jenaj metodoj:\n:* Elŝutu [https://www.mediawiki.org/wiki/Download pakitan instalilon], kiu enhavas kelkajn etosojn kaj etendaĵojn. Vi povas de ĝi kopii kaj alglui la dosierujon <code>skins/</code>.\n:* Elŝutu unuopajn pakitajn etosojn de [https://www.mediawiki.org/wiki/Special:SkinDistributor mediawiki.org].\n:* [https://www.mediawiki.org/wiki/Download_from_Git#Using_Git_to_download_MediaWiki_skins Uzu Git por elŝuti etosojn].\n: Tio maldevus interkolizii kun via git-deponejo se vi estas evoluiganto de MediaWiki.\n\n; Se vi ĵus promociis MediaWiki:\n: MediaWiki 1.24 kaj pli novaj ne plu aŭtomate ŝaltas instalitajn etosojn (vidu [https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery Manlibro:Aŭtomata malkovrado de etosoj]). Vi povas alglui {{PLURAL:$5|jenan linion|jenajn liniojn}} al <code>LocalSettings.php</code> por ŝalti {{PLURAL:$5|la instalitan etoson|ĉiujn instalitajn etosojn}}:\n\n<pre dir=\"ltr\">$3</pre>\n\n; Se vi ĵus modifis <code>LocalSettings.php</code>:\n: Denove kontrolu nomon de etosoj pro eblaj mistajpoj.",
        "mediastatistics-header-text": "Tekstaj",
        "mediastatistics-header-executable": "Plenumeblaj dosieroj",
        "mediastatistics-header-archive": "Densigitaj formoj",
+       "mediastatistics-header-total": "Ĉiuj dosieroj",
        "json-warn-trailing-comma": "De JSON estis {{PLURAL:$1|forigita fina komo|forigitaj finaj komoj}}",
        "json-error-unknown": "Okazis problemo pri JSON. Eraro: $1",
        "json-error-depth": "Maksimuma profundeco de stako estis superita",
        "special-characters-group-ipa": "IPA",
        "special-characters-group-symbols": "Simboloj",
        "special-characters-group-greek": "Greka",
+       "special-characters-group-greekextended": "Greka etendita",
        "special-characters-group-cyrillic": "Cirila",
        "special-characters-group-arabic": "Araba",
        "special-characters-group-arabicextended": "araba etendite",
        "special-characters-title-endash": "mallonga streketo",
        "special-characters-title-emdash": "longa streketo",
        "special-characters-title-minus": "minus-signo",
+       "mw-widgets-dateinput-no-date": "Neniu dato elektita",
        "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-TT",
        "mw-widgets-dateinput-placeholder-month": "JJJJ-MM",
        "mw-widgets-titleinput-description-new-page": "paĝo ankoraŭ ne ekzistas",
index 7fe4263..649a83a 100644 (file)
        "createaccountreason": "Motivo:",
        "createacct-reason": "Motivo",
        "createacct-reason-ph": "Por qué estás creando otra cuenta",
+       "createacct-reason-help": "Mensaje que se muestra en el registro de creación de cuentas",
        "createacct-submit": "Crea tu cuenta",
        "createacct-another-submit": "Crear cuenta",
        "createacct-benefit-heading": "Personas como tú son las que construyen {{SITENAME}}.",
        "nocookiesnew": "Se ha creado la cuenta de usuario, pero aún no has iniciado sesión.\n{{SITENAME}} usa <em>cookies</em> para identificar a los usuarios registrados.\nTu navegador tiene desactivadas las cookies.\nPor favor, actívalas e inicia sesión con tu nuevo nombre de usuario y contraseña.",
        "nocookieslogin": "{{SITENAME}} utiliza <em>cookies</em> para la autenticación de usuarios. Las <em>cookies</em> están desactivadas en tu navegador. Por favor, actívalas e inténtalo de nuevo.",
        "nocookiesfornew": "No se pudo crear la cuenta de usuario, porque no pudimos confirmar su origen.\nAsegúrate de que tienes las cookies activadas, luego recarga esta página e inténtalo de nuevo.",
+       "createacct-loginerror": "La cuenta se ha creado correctamente, pero no se pudo ingresar automáticamente. Procede al [[Special:UserLogin|acceso manual]].",
        "noname": "No se ha especificado un nombre de usuario válido.",
        "loginsuccesstitle": "Has accedido",
        "loginsuccess": "<strong>Has accedido a {{SITENAME}} como «$1».</strong>",
        "log-action-filter-suppress-block": "Usuario supppression por bloque",
        "log-action-filter-suppress-reblock": "Usuario supresión de rebloqueo",
        "log-action-filter-upload-upload": "Subida nueva",
-       "log-action-filter-upload-overwrite": "Volver a subir"
+       "log-action-filter-upload-overwrite": "Volver a subir",
+       "authmanager-authplugin-setpass-failed-message": "El complemento de autenticación denegó el cambio de contraseña.",
+       "authmanager-authplugin-create-fail": "El complemento de autenticación denegó la creación de la cuenta.",
+       "authmanager-userdoesnotexist": "El usuario «$1» no está registrado.",
+       "authmanager-password-help": "Contraseña para autenticación.",
+       "authmanager-email-label": "Correo electrónico",
+       "authmanager-email-help": "Dirección de correo electrónico",
+       "authmanager-realname-label": "Nombre real",
+       "authmanager-realname-help": "Nombre real del usuario",
+       "authmanager-provider-password": "Autenticación basada en contraseña",
+       "authprovider-confirmlink-success-line": "$1: vinculado exitosamente.",
+       "authprovider-resetpass-skip-label": "Omitir",
+       "authform-nosession-login": "La autenticación fue exitosa, pero tu navegador no puede \"recordar\" haber registrado.",
+       "specialpage-securitylevel-not-allowed": "Lo siento, no tienes permitido usar esta página, porque tu identidad no pudo verificarse.",
+       "authpage-cannot-login-continue": "No se puede continuar con el inicio de sesión. Lo más probable es que tu sesión haya expirado.",
+       "changecredentials": "Cambiar las credenciales",
+       "changecredentials-submit": "Cambiar",
+       "changecredentials-submit-cancel": "Cancelar",
+       "removecredentials-submit": "Eliminar",
+       "removecredentials-submit-cancel": "Cancelar",
+       "credentialsform-account": "Nombre de la cuenta:",
+       "cannotlink-no-provider": "No hay cuentas vinculables.",
+       "linkaccounts": "Vincular cuentas",
+       "linkaccounts-success-text": "La cuenta fue vinculada.",
+       "linkaccounts-submit": "Vincular cuentas",
+       "unlinkaccounts": "Desvincular cuentas"
 }
index c254685..5b0317c 100644 (file)
        "authprovider-resetpass-skip-label": "Sauter",
        "authprovider-resetpass-skip-help": "Sauter la réinitialisation du mot de passe.",
        "authform-nosession-login": "L’authentification aréussi, mais votre navigateur ne peut pas se « souvenir » d’avoir été connecté.\n\n$1",
-       "authform-nosession-signup": "Le compte a été créé, mais votre navigateur ne peut pas se « souvenir » avoir été connecté.",
+       "authform-nosession-signup": "Le compte a été créé, mais votre navigateur ne peut pas se « souvenir » avoir été connecté.\n\n$1",
        "authform-newtoken": "Jeton manquant. $1",
        "authform-notoken": "Jeton manquant",
        "authform-wrongtoken": "Mauvais jeton",
        "specialpage-securitylevel-not-allowed": "Désolé, vous n’êtes pas autorisé à utiliser cette page car votre identité n’a pu être vérifiée.",
        "authpage-cannot-login": "Impossible de démarrer la connexion.",
        "authpage-cannot-login-continue": "Impossible de continuer la connexion. Votre session a certainement expiré.",
-       "authpage-cannot-create": "Impossible de commencer la création de compte."
+       "authpage-cannot-create": "Impossible de commencer la création de compte.",
+       "authpage-cannot-create-continue": "Impossible de poursuivre la création du compte. Votre session a sans doute expiré.",
+       "authpage-cannot-link": "Impossible de commencer la liaison du compte.",
+       "authpage-cannot-link-continue": "Impossible de continuer la liaison du compte. Votre session a sans doute expiré.",
+       "cannotauth-not-allowed-title": "Autorisation refusée",
+       "cannotauth-not-allowed": "Vous n’êtes pas autorisé à utiliser cette page",
+       "changecredentials": "Modifier les informations d’identification",
+       "changecredentials-submit": "Modifier",
+       "changecredentials-submit-cancel": "Annuler",
+       "changecredentials-invalidsubpage": "$1 n’est pas un type d’information d’identification valide.",
+       "changecredentials-success": "Vos informations d’identification ont été modifiées.",
+       "removecredentials": "Supprimer les informations d’identification",
+       "removecredentials-submit": "Supprimer",
+       "removecredentials-submit-cancel": "Annuler",
+       "removecredentials-invalidsubpage": "$1 n’est pas un type d’information d’identification valide.",
+       "removecredentials-success": "Vos informations d’identification ont été supprimées.",
+       "credentialsform-provider": "Type d’information d’identification :",
+       "credentialsform-account": "Nom de compte :",
+       "cannotlink-no-provider-title": "Il n’y a pas de comptes pouvant être liés",
+       "cannotlink-no-provider": "Il n’y a pas de compte pouvant être lié.",
+       "linkaccounts": "Lier les comptes",
+       "linkaccounts-success-text": "Le compte a été lié.",
+       "linkaccounts-submit": "Lier les comptes",
+       "unlinkaccounts": "Dissocier les comptes",
+       "unlinkaccounts-success": "Le compte a été dissocié."
 }
index 0fda562..43ccccb 100644 (file)
        "password-change-forbidden": "Non pode mudar os contrasinais neste wiki.",
        "externaldberror": "Ou ben se produciu un erro da base de datos na autenticación externa ou ben non se lle permite actualizar a súa conta externa.",
        "login": "Acceder ao sistema",
+       "login-security": "Verifique a súa identidade",
        "nav-login-createaccount": "Rexistro",
        "userlogin": "Rexistro",
        "userloginnocreate": "Rexistro",
        "userlogin-resetpassword-link": "Esqueceu o contrasinal?",
        "userlogin-helplink2": "Axuda co rexistro",
        "userlogin-loggedin": "Xa accedeu ao sistema como {{GENDER:$1|$1}}.\nUtilice o formulario inferior para acceder como outro usuario.",
+       "userlogin-reauth": "Debe conectarse de novo para verificar que é {{GENDER:$1|$1}}.",
        "userlogin-createanother": "Crear outra conta",
        "createacct-emailrequired": "Enderezo de correo electrónico",
        "createacct-emailoptional": "Enderezo de correo electrónico (opcional)",
        "createacct-email-ph": "Insira o seu enderezo de correo electrónico",
        "createacct-another-email-ph": "Insira o enderezo de correo electrónico",
        "createaccountmail": "Utilizar un contrasinal aleatorio temporal e envialo ao enderezo de correo electrónico especificado",
+       "createaccountmail-help": "Pode usarse para crear unha conta para outra persoa sen coñecer o contrasinal.",
        "createacct-realname": "Nome real (opcional)",
        "createaccountreason": "Motivo:",
        "createacct-reason": "Motivo",
        "createacct-reason-ph": "Por que crea outra conta?",
+       "createacct-reason-help": "Mensaxe que se mostra no rexistro de creación de contas",
        "createacct-submit": "Crear a conta",
        "createacct-another-submit": "Crear conta",
+       "createacct-continue-submit": "Continuar a creación da conta",
+       "createacct-another-continue-submit": "Continuar a creación da conta",
        "createacct-benefit-heading": "Xente coma vostede elabora {{SITENAME}}.",
        "createacct-benefit-body1": "{{PLURAL:$1|edición|edicións}}",
        "createacct-benefit-body2": "{{PLURAL:$1|páxina|páxinas}}",
        "nocookiesnew": "A conta de usuario foi creada, pero non accedeu ao sistema.\n{{SITENAME}} para rexistrar os usuarios.\nVostede ten as cookies deshabilitadas.\nPor favor, habilíteas e logo acceda ao sistema co seu novo nome de usuario e contrasinal.",
        "nocookieslogin": "{{SITENAME}} usa cookies para rexistrar os usuarios.\nVostede ten as cookies deshabilitadas.\nPor favor, habilíteas e inténteo de novo.",
        "nocookiesfornew": "Non se creou a conta de usuario porque non puidemos confirmar a súa orixe.\nAsegúrese de que ten as cookies habilitadas, volva cargar a páxina e inténteo de novo.",
+       "createacct-loginerror": "A conta creouse correctamente, pero non se puido conectar automaticamente. Proceda ó [[Special:UserLogin|acceso manual]].",
        "noname": "Non especificou un nome de usuario válido.",
        "loginsuccesstitle": "Conectado",
        "loginsuccess": "<strong>Accedeu ao sistema de {{SITENAME}} como \"$1\".</strong>",
-       "nosuchuser": "Non existe ningún usuario chamado \"$1\".\nOs nomes de usuario diferencian entre maiúsculas e minúsculas.\nVerifique o nome que inseriu ou [[Special:CreateAccount|cree unha nova conta]].",
+       "nosuchuser": "Non existe ningún usuario chamado \"$1\".\nOs nomes de usuario diferencian entre maiúsculas e minúsculas.\nVerifique a ortografía ou [[Special:CreateAccount|cree unha nova conta]].",
        "nosuchusershort": "Non existe ningún usuario chamado \"$1\".\nVerifique o nome que inseriu.",
        "nouserspecified": "Cómpre especificar un nome de usuario.",
        "login-userblocked": "Este usuario está bloqueado. Acceso non autorizado.",
        "createacct-another-realname-tip": "O nome real é opcional.\nSe escolle dalo utilizarase para atribuír ao usuario o seu traballo.",
        "pt-login": "Acceder ao sistema",
        "pt-login-button": "Acceder ao sistema",
+       "pt-login-continue-button": "Continuar a conexión",
        "pt-createaccount": "Crear unha conta",
        "pt-userlogout": "Saír",
        "php-mail-error-unknown": "Erro descoñecido na función mail() do PHP.",
        "botpasswords-invalid-name": "O nome de usuario especificado non contén o separador de contrasinal de bot (\"$1\").",
        "botpasswords-not-exist": "O usuario \"$1\" non ten un contrasinal de bot de nome \"$2\".",
        "resetpass_forbidden": "Non se poden mudar os contrasinais",
+       "resetpass_forbidden-reason": "Os contrasinais non poden cambiarse: $1",
        "resetpass-no-info": "Debe rexistrarse para acceder directamente a esta páxina.",
        "resetpass-submit-loggedin": "Cambiar o contrasinal",
        "resetpass-submit-cancel": "Cancelar",
        "passwordreset-emailsentusername": "Se hai unha dirección de correo electrónico asociada con este nome de usuario, entón enviarase un correo electrónico para o restablecemento do contrasinal.",
        "passwordreset-emailsent-capture": "Enviouse un correo electrónico de restablecemento do contrasinal, mostrado a continuación.",
        "passwordreset-emailerror-capture": "Xerouse un correo electrónico de restablecemento do contrasinal, mostrado a continuación, pero o envío {{GENDER:$2|ao usuario|á usuaria}} fallou: $1",
+       "passwordreset-emailsent-capture2": "{{PLURAL:$1|O correo de reinicialización do contrasinal foi enviado|Os correos de reinicialización do contrasinal foron enviados}}. {{PLURAL:$1|O nome de usuario e contrasinal móstrase abaixo|A lista de nomes de usuarios e contrasinais móstranse abaixo}}.",
+       "passwordreset-emailerror-capture2": "O envío do correo {{GENDER:$2|ó usuario|á usuaria}} fallou: $1 {{PLURAL:$3|O nome de usuario e contrasinal móstrase abaixo|A lista de usuarios e contrasinais móstranse abaixo}}.",
+       "passwordreset-ignored": "A reinicialización do contrasinal non puido realizarse. Quizais non configurou o provedor?",
+       "passwordreset-invalideamil": "O enderezo de correo electrónico non é válido",
+       "passwordreset-nodata": "Non se indicou o nome de usuario ou a dirección de correo electrónico",
        "changeemail": "Cambiar ou eliminar o enderezo de correo electrónico",
        "changeemail-header": "Encha este formulario para cambiar o seu enderezo de correo electrónico. Se vostede quere eliminar a asociación da dirección de correo electrónico da súa conta, deixe en branco a nova dirección de correo electrónico cando envíe o formulario.",
        "changeemail-passwordrequired": "Terá que escribir o seu contrasinal para confirmar este cambio.",
        "accmailtext": "Un contrasinal xerado ao chou para [[User talk:$1|$1]] foi enviado a $2. Pode modificarse na páxina de [[Special:ChangePassword|cambio de contrasinais]] tras acceder ao sistema.",
        "newarticle": "(Novo)",
        "newarticletext": "Seguiu unha ligazón a unha páxina que aínda non existe.\nPara crear a páxina, comece a escribir na caixa inferior (consulte a [$1 páxina de axuda] para obter máis información).\nSe chegou aquí por erro, simplemente prema no botón '''atrás''' do seu navegador.",
-       "anontalkpagetext": "----''Esta é a páxina de conversa dun usuario anónimo que aínda non creou unha conta ou que non a usa. Polo tanto, empregamos o enderezo IP para a súa identificación. Este enderezo IP pódeno compartir varios usuarios distintos. Se pensa que foron dirixidos contra a súa persoa comentarios inadecuados, por favor, [[Special:CreateAccount|cree unha conta]] ou [[Special:UserLogin|acceda ao sistema]] para evitar futuras confusións con outros usuarios anónimos.''",
+       "anontalkpagetext": "----\n<em>Esta é a páxina de conversa dun usuario anónimo que aínda non creou unha conta ou que non a usa.</em> Polo tanto, empregamos o enderezo IP para a súa identificación. Este enderezo IP pódeno compartir varios usuarios distintos. Se pensa que foron dirixidos contra a súa persoa comentarios inadecuados, por favor, [[Special:CreateAccount|cree unha conta]] ou [[Special:UserLogin|acceda ao sistema]] para evitar futuras confusións con outros usuarios anónimos.",
        "noarticletext": "Actualmente non hai ningún texto nesta páxina.\nPode [[Special:Search/{{PAGENAME}}|procurar polo título desta páxina]] noutras páxinas,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ollar os rexistros relacionados]\nou [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear a páxina]</span>.",
        "noarticletext-nopermission": "Actualmente non hai ningún texto nesta páxina.\nPode [[Special:Search/{{PAGENAME}}|procurar polo título desta páxina]] noutras páxinas ou <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} ollar os rexistros relacionados]</span>, pero non ten os permisos necesarios para crear esta páxina.",
        "missing-revision": "A revisión nº$1 da páxina chamada \"{{FULLPAGENAME}}\" non existe.\n\nA miúdo, isto está provocado por seguir unha ligazón de historial obsoleta cara a unha páxina que foi borrada.\nO [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rexistro de borrados] contén máis detalles.",
        "log-action-filter-suppress-block": "Supresión de usuario por bloqueo",
        "log-action-filter-suppress-reblock": "Supresión de usuario por bloqueo reiterado",
        "log-action-filter-upload-upload": "Nova subida",
-       "log-action-filter-upload-overwrite": "Resubida"
+       "log-action-filter-upload-overwrite": "Resubida",
+       "authmanager-authn-no-primary": "A información de identificación proporcionada non pode ser autenticada.",
+       "authmanager-email-label": "Correo electrónico",
+       "authmanager-email-help": "Enderezo de correo electrónico",
+       "authmanager-realname-label": "Nome real",
+       "authmanager-realname-help": "Nome real do usuario",
+       "authmanager-provider-temporarypassword": "Contrasinal temporal",
+       "authprovider-resetpass-skip-label": "Omitir",
+       "authprovider-resetpass-skip-help": "Saltar a reinicialización do contrasinal.",
+       "specialpage-securitylevel-not-allowed-title": "Non permitido",
+       "changecredentials-submit": "Cambiar",
+       "changecredentials-submit-cancel": "Cancelar",
+       "removecredentials-submit": "Eliminar",
+       "removecredentials-submit-cancel": "Cancelar",
+       "credentialsform-account": "Nome da conta:"
 }
index 428c8dd..f63874f 100644 (file)
@@ -48,7 +48,7 @@
        "tog-showtoolbar": "הצגת סרגל העריכה",
        "tog-editondblclick": "עריכת דפים בלחיצה כפולה",
        "tog-editsectiononrightclick": "עריכת פסקאות באמצעות לחיצה ימנית על כותרות הפסקאות",
-       "tog-watchcreations": "הוספת דפים שאני {{GENDER:|יוצר|יוצרת}} וקבצים שאני מעלה לרשימת המעקב",
+       "tog-watchcreations": "×\94×\95ספת ×\93פ×\99×\9d ×\97×\93ש×\99×\9d ×©×\90× ×\99 {{GENDER:|×\99×\95צר|×\99×\95צרת}} ×\95ק×\91צ×\99×\9d ×©×\90× ×\99 ×\9e×¢×\9c×\94 ×\9cרש×\99×\9eת ×\94×\9eעק×\91",
        "tog-watchdefault": "הוספת דפים וקבצים שאני {{GENDER:|עורך|עורכת}} לרשימת המעקב",
        "tog-watchmoves": "הוספת דפים וקבצים שאני {{GENDER:|מעביר|מעבירה}} לרשימת המעקב",
        "tog-watchdeletion": "הוספת דפים וקבצים שאני {{GENDER:|מוחק|מוחקת}} לרשימת המעקב",
        "go": "הצגה",
        "searcharticle": "לדף",
        "history": "היסטוריית הדף",
-       "history_short": "×\92רס×\90×\95ת ×§×\95×\93×\9e×\95ת",
+       "history_short": "×\94×\99ס×\98×\95ר×\99×\94",
        "updatedmarker": "עודכן מאז ביקורך האחרון",
        "printableversion": "גרסת הדפסה",
        "permalink": "קישור קבוע",
        "confirmable-confirm": "האם {{GENDER:$1|ברצונך}} להמשיך?",
        "confirmable-yes": "כן",
        "confirmable-no": "לא",
-       "thisisdeleted": "×\9cש×\97×\96ר ×\90×\95 ×\9c×\94צ×\99×\92 $1?",
+       "thisisdeleted": "×\9c×\94צ×\99×\92 ×\90×\95 ×\9cש×\97×\96ר $1?",
        "viewdeleted": "להציג $1?",
        "restorelink": "{{PLURAL:$1|גרסה מחוקה אחת|$1 גרסאות מחוקות}}",
        "feedlinks": "הזנה:",
        "virus-badscanner": "הגדרות שגויות: סורק הווירוסים אינו ידוע: ''$1''",
        "virus-scanfailed": "הסריקה נכשלה (קוד: $1)",
        "virus-unknownscanner": "אנטי־וירוס בלתי ידוע:",
-       "logouttext": "<strong>×\99צ×\90ת×\9d ×\96×\94 ×¢×ª×\94 ×\9e×\94×\97ש×\91×\95×\9f.</strong>\n\nש×\99×\9e×\95 ×\9c×\91 ×\9b×\99 ×\99×\99ת×\9b×\9f ×©×\93פ×\99×\9d ×\90×\97×\93×\99×\9d ×\99×\9eש×\99×\9b×\95 ×\9c×\94×\99×\95ת ×\9e×\95צ×\92×\99×\9d ×\9b×\90×\99×\9c×\95 ×\90ת×\9d ×¢×\93×\99×\99×\9f ×\9e×\97×\95×\91ר×\99×\9d ×\9c×\97ש×\91×\95×\9f עד שתנקו את המטמון של הדפדפן שלכם.",
+       "logouttext": "<strong>×\99צ×\90ת×\9d ×\9e×\94×\97ש×\91×\95×\9f.</strong>\n\nש×\99×\9e×\95 ×\9c×\91 ×©×\99×\99ת×\9b×\9f ×©×\93פ×\99×\9d ×\9eס×\95×\99×\9e×\99×\9d ×\99×\9eש×\99×\9b×\95 ×\9c×\94×\99×\95ת ×\9e×\95צ×\92×\99×\9d ×\9b×\90×\99×\9c×\95 ×\90ת×\9d ×¢×\93×\99×\99×\9f ×\9e×\97×\95×\91ר×\99×\9d ×\9c×\97ש×\91×\95×\9f, עד שתנקו את המטמון של הדפדפן שלכם.",
        "cannotlogoutnow-title": "לא ניתן לצאת מהחשבון עכשיו",
        "cannotlogoutnow-text": "היציאה אינה אפשרית בעת שימוש ב{{GRAMMAR:תחילית|$1}}.",
        "welcomeuser": "ברוך בואך, $1!",
        "password-change-forbidden": "אין באפשרותך לשנות סיסמאות באתר זה.",
        "externaldberror": "אירעה שגיאת אימות בבסיס הנתונים, או שאינך מורשה לעדכן את החשבון החיצוני שלך.",
        "login": "כניסה לחשבון",
-       "login-security": "× ×\90 ×\9c×\90×\9eת ×\90ת ×\96×\94×\95תך",
+       "login-security": "×\90×\99×\9e×\95ת ×\94×\96×\94×\95ת ×©×\9cך",
        "nav-login-createaccount": "כניסה לחשבון / הרשמה",
        "userlogin": "כניסה לחשבון / הרשמה",
        "userloginnocreate": "כניסה לחשבון",
        "passwordreset-emailerror-capture": "נוצר דואר אלקטרוני לאיפוס הסיסמה, והוא מוצג להלן, אך שליחתו ל{{GENDER:$2|משתמש|משתמשת}} נכשלה: $1",
        "passwordreset-emailsent-capture2": "{{PLURAL:$1|דוא\"ל איפוס הסיסמה נשלח|הודעות דוא\"ל של איפוס הסיסמה נשלחו}}. {{PLURAL:$1|שם המשתמשים והסיסמה מוצגים|רשימה של שמות המשתמשים והסיסמאות מוצגת}} להלן.",
        "passwordreset-emailerror-capture2": "לא ניתן היה לשלוח דוא\"ל ל{{GENDER:$2|משתמש|משתמשת}}: $1 {{PLURAL:$3|שם המשתמש והסיסמה מוצגים|רשימה של שמות המשתמשים והסיסמאות מוצגת}} להלן.",
-       "passwordreset-nocaller": "×\97×\95×\91×\94 ×\9cתת ×§×\95ר×\90",
-       "passwordreset-nosuchcaller": "×\94×\95קרא אינו קיים: $1",
+       "passwordreset-nocaller": "×\9c×\90 ×¡×\95פק ×\94ק×\95ר×\90 ×\94× ×\93רש",
+       "passwordreset-nosuchcaller": "×\94ק×\95רא אינו קיים: $1",
        "passwordreset-ignored": "איפוס הסיסמה לא בוצע. ייתכן שלא הוגדר ספק.",
        "passwordreset-invalideamil": "כתובת דוא\"ל לא תקינה",
        "passwordreset-nodata": "לא סופק שם משתמש או כתובת דוא\"ל",
        "grant-viewmywatchlist": "צפייה ברשימת המעקב שלך",
        "newuserlogpage": "יומן רישום משתמשים",
        "newuserlogpagetext": "זהו יומן המכיל הרשמות של משתמשים.",
-       "rightslog": "×\99×\95×\9e×\9f ×ª×¤×§×\99×\93×\99×\9d",
+       "rightslog": "×\99×\95×\9e×\9f ×\94רש×\90×\95ת",
        "rightslogtext": "זהו יומן השינויים בתפקידי המשתמשים.",
        "action-read": "לקרוא דף זה",
        "action-edit": "לערוך דף זה",
        "ipb-confirmhideuser": "אתם עומדים לחסום משתמש עם האפשרות \"הסתרת משתמש\". זה יעלים את שם המשתמש בכל הרשימות ופעולות היומן. האם אתם בטוחים שברצונכם לעשות זאת?",
        "ipb-confirmaction": "אם אתם באמת בטוחים שברצונכם לעשות זאת, אנא סמנו את השדה \"{{int:ipb-confirm}}\" שבתחתית.",
        "ipb-edit-dropdown": "עריכת סיבות החסימה",
-       "ipb-unblock-addr": "×\91×\99×\98×\95×\9c חסימה של $1",
-       "ipb-unblock": "×\91×\99×\98×\95×\9c ×\97ס×\99×\9e×\94 ×©×\9c ×©×\9d ×\9eשת×\9eש ×\90×\95 כתובת IP",
+       "ipb-unblock-addr": "ש×\97ר×\95ר חסימה של $1",
+       "ipb-unblock": "ש×\97ר×\95ר ×\97ס×\99×\9e×\94 ×©×\9c ×\9eשת×\9eש ×\90×\95 ×©×\9c כתובת IP",
        "ipb-blocklist": "הצגת החסימות הנוכחיות",
        "ipb-blocklist-contribs": "תרומות של {{GENDER:$1|$1}}",
        "ipb-blocklist-duration-left": "נותרו $1",
-       "unblockip": "שחרור חסימה",
+       "unblockip": "שחרור חסימה של משתמש",
        "unblockiptext": "השתמשו בטופס שלהלן כדי להחזיר את הרשאות הכתיבה למשתמש או כתובת IP חסומים.",
-       "ipusubmit": "ש×\97ר×\95ר ×\97ס×\99×\9e×\94",
+       "ipusubmit": "×\94סרת ×\97ס×\99×\9e×\94 ×\96×\95",
        "unblocked": "[[User:$1|$1]] {{GENDER:$1|שוחרר מחסימתו|שוחררה מחסימתה}}.",
        "unblocked-range": "$1 שוחרר מחסימתו.",
        "unblocked-id": "חסימה מספר $1 שוחררה.",
        "markedaspatrollederror": "לא ניתן לסמן כבדוק",
        "markedaspatrollederrortext": "יש לציין גרסה שברצונך לסמן כבדוקה.",
        "markedaspatrollederror-noautopatrol": "אינך מורשה לסמן שינויים של עצמך כבדוקים.",
-       "markedaspatrollednotify": "ער×\99×\9b×\94 ×\96×\95 ×\91×\93×£ \"$1\" ×¡×\95×\9e× ×\94 ×\9b×\91×\93×\95ק×\94.",
-       "markedaspatrollederrornotify": "ס×\99×\9e×\95×\9f ×\94ער×\99×\9b×\94 ×\9b×\91×\93×\95ק×\94 נכשל.",
+       "markedaspatrollednotify": "ש×\99× ×\95×\99 ×\96×\94 ×\9c\"$1\" ×¡×\95×\9e×\9f ×\9b×\91×\93×\95ק.",
+       "markedaspatrollederrornotify": "ס×\99×\9e×\95×\9f ×\94ש×\99× ×\95×\99 ×\9b×\91×\93×\95ק נכשל.",
        "patrol-log-page": "יומן שינויים בדוקים",
        "patrol-log-header": "יומן זה מציג גרסאות שנבדקו.",
        "log-show-hide-patrol": "$1 יומן שינויים בדוקים",
        "dberr-usegoogle": "באפשרותך לנסות לחפש באמצעות גוגל בינתיים.",
        "dberr-outofdate": "שימו לב שהתוכן שלנו כפי שנשמר במאגר שם עשוי שלא להיות מעודכן.",
        "dberr-cachederror": "זהו עותק שמור של המידע, והוא עשוי שלא להיות מעודכן.",
-       "htmlform-invalid-input": "×\99ש ×\91×¢×\99×\95ת ×¢×\9d ×\97×\9cק ×\9e×\94ק×\9c×\98 ×©×\94×\9bנסת",
+       "htmlform-invalid-input": "×\99ש ×\91×¢×\99×\95ת ×¢×\9d ×\97×\9cק ×\9e×\94ק×\9c×\98 ×©×\94×\95×\9bנס.",
        "htmlform-select-badoption": "הערך שציינת אינו אפשרות תקינה.",
        "htmlform-int-invalid": "הערך שציינת אינו מספר שלם.",
        "htmlform-float-invalid": "הערך שציינת אינו מספר.",
        "htmlform-int-toolow": "הערך שציינת הוא מתחת למינימום, $1",
        "htmlform-int-toohigh": "הערך שציינת הוא מעל למקסימום, $1",
-       "htmlform-required": "ער×\9a ×\96×\94 ×\93ר×\95ש",
+       "htmlform-required": "ש×\93×\94 ×\96×\94 ×\93ר×\95ש.",
        "htmlform-submit": "שליחה",
        "htmlform-reset": "ביטול השינויים",
        "htmlform-selectorother-other": "אחר",
        "log-action-filter-upload": "סוג ההעלאות:",
        "log-action-filter-all": "הכול",
        "log-action-filter-block-block": "חסימות",
-       "log-action-filter-block-reblock": "ש×\99× ×\95×\99×\99 ×\97ס×\99×\9e×\94",
-       "log-action-filter-block-unblock": "ש×\97ר×\95ר×\99 ×\97ס×\99×\9e×\94",
+       "log-action-filter-block-reblock": "ש×\99× ×\95×\99×\99 ×\97ס×\99×\9e×\95ת",
+       "log-action-filter-block-unblock": "ש×\97ר×\95ר×\99 ×\97ס×\99×\9e×\95ת",
        "log-action-filter-contentmodel-change": "שינויים במודל תוכן",
        "log-action-filter-contentmodel-new": "יצירות דפים עם מודל תוכן לא־סטנדרטי",
        "log-action-filter-delete-delete": "מחיקת דפים",
index ca80da8..c012d7b 100644 (file)
        "exif-sublocationcreated": "Sublocalitate del citate ubi le photo esseva prendite",
        "exif-worldregiondest": "Region del mundo monstrate",
        "exif-countrydest": "Pais monstrate",
-       "exif-countrycodedest": "Codice pro pais monstrate",
+       "exif-countrycodedest": "Codice del pais monstrate",
        "exif-provinceorstatedest": "Provincia o stato monstrate",
        "exif-citydest": "Citate monstrate",
        "exif-sublocationdest": "Sublocalitate del citate monstrate",
index 553dd36..9c06301 100644 (file)
@@ -37,8 +37,8 @@
        "tog-watchuploads": "Wuwuh barkas anyar unggahanku nyang pawawanganku",
        "tog-watchrollback": "Wuwuh kaca sing tak wurungaké nyang pawawanganku",
        "tog-minordefault": "Tengeri kabèh besutan minangka besutan cilik sacara baku",
-       "tog-previewontop": "Deleng prawuryan sadurungé besut kothak",
-       "tog-previewonfirst": "Tuduhaké prawuryan nalika mbesut pisanan",
+       "tog-previewontop": "Deleng pratuduh sadurungé mbesut kothak",
+       "tog-previewonfirst": "Delelng pratuduh nalika mbesut pisanan",
        "tog-enotifwatchlistpages": "Kirimi aku layangtronik yèn ana kaca utawa barkas ing pawawanganku sing diowah",
        "tog-enotifusertalkpages": "Kirimi aku layangtronik yèn kaca gegunemanku diowah",
        "tog-enotifminoredits": "Uga kirimi aku layangtronik yèn ana besutan cilik ing kaca lan barkas",
@@ -46,7 +46,7 @@
        "tog-shownumberswatching": "Tuduhaké cacah wong sing ngawasi",
        "tog-oldsig": "Tandha tangan sing ana:",
        "tog-fancysig": "Anggep tandha tangan minangka tulisan wiki (tanpa pranala otomatis)",
-       "tog-uselivepreview": "Trapaké prawuryan langsung",
+       "tog-uselivepreview": "Nganggo pratuduh langsung",
        "tog-forceeditsummary": "Élingna aku menawa kothak ringkesan suntingan isih kosong",
        "tog-watchlisthideown": "Dhelikaké besutanku saka pawawangan",
        "tog-watchlisthidebots": "Dhelikaké besutan bot saka pangawasan",
        "minoredit": "Iki besutan cilik",
        "watchthis": "Awasi kaca iki",
        "savearticle": "Simpen kaca",
-       "preview": "Prawuryan",
-       "showpreview": "Tuduhaké prawuryan",
+       "preview": "Pratuduh",
+       "showpreview": "Deleng pratuduh",
        "showdiff": "Tuduhaké owahan",
        "anoneditwarning": "<strong>Penget:</strong> Panjenengan boten mlebet log. Alamat IP Panjenengan badhe katingal dening publik manawi Panjenengan ngayahi ewah-ewahan. Manawi Panjenengan  <strong>[$1 mlebet log]</strong> utawai <strong>[$2 damel akun]</strong>, suntingan Panjenengan badhe kaatribusekaken dhumateng  nama pangangge Panjenengan, lan rupi-rupi  kauntungan sanesipun.",
        "anonpreviewwarning": "''Sampéyan durung mlebu log. Nyimpen bakal nyathet alamat IP Sampéyan nèng riwayat sunting kaca iki.''",
        "missingsummary": "'''Pènget:''' Panjenengan ora nglebokaké ringkesan panyuntingan. Menawa panjenengan mencèt tombol Simpen manèh, suntingan panjenengan bakal kasimpen tanpa ringkesan panyuntingan.",
        "missingcommenttext": "Tulung lebokna komentar ing ngisor iki.",
        "missingcommentheader": "'''Pangéling:''' Sampéyan durung nyadhiyakaké judhul/jejer kanggo tanggepan iki.\nYèn Sampéyan klik \"{{int:savearticle}}\" manèh, suntingan Sampéyan bakal kasimpen tanpa kuwi.",
-       "summary-preview": "Prawuryan tingkesan:",
+       "summary-preview": "Pratuduh tingkesan:",
        "subject-preview": "Prawuryaning jejer:",
        "blockedtitle": "Panganggo kapalangan",
        "blockedtext": "<b>Asma panganggo utawa alamat IP panjenengan diblokir.</b>\n\nBlokir iki sing nglakoni $1.\nAlesané <i>$2</i>.\n\n* Diblokir wiwit: $8\n* Kadaluwarsa pemblokiran ing: $6\n* Sing arep diblokir: $7\n\nPanjenengan bisa ngubungi $1 utawa [[{{MediaWiki:Grouppage-sysop}}|pangurus liyané]] kanggo ngomongaké prakara iki.\n\nPanjenengan ora bisa nggunakaké fitur 'Kirim layang é-mail panganggo iki' kejaba panjenengan wis nglebokaké alamat é-mail sing sah ing [[Special:Preferences|prèferènsi]] panjenengan.\n\nAlamat IP panjenengan iku $3, lan ID pamblokiran iku #$5.\nTulung kabèh informasi ing ndhuwur iki disertakaké ing saben pitakon panjenengan.",
        "prefs-edits": "Gunggung besutan:",
        "prefsnologintext2": "Tulung $1 kanggo ngganti preferensi sampeyan.",
        "prefs-skin": "Kulit",
-       "skin-preview": "Prawuryan",
+       "skin-preview": "Pratuduh",
        "datedefault": "Ora ana pilihan",
        "prefs-labs": "Piranti lab",
        "prefs-user-pages": "Kacaning sing nganggo",
        "tooltip-ca-nstab-category": "Deleng kaca kategori",
        "tooltip-minoredit": "Tandhani iki minangka besutan cilik",
        "tooltip-save": "Simpen owah-owahaning sampéyan",
-       "tooltip-preview": "Prawuryan owah-owahaning sampéyan. Anggoa cara iki sadurungé nyimpen.",
+       "tooltip-preview": "Pratuduhing owah-owahaning sampéyan. Anggoa cara iki sadurungé nyimpen.",
        "tooltip-diff": "Tuduhaké owah-owahan endi sing sampéyan gawé tumrap tulisan iki",
        "tooltip-compareselectedversions": "Delengen prabédan antara rong vèrsi kaca iki sing dipilih.",
        "tooltip-watch": "Wuwuh kaca iki nyang pawawanganing sampéyan",
        "svg-long-desc-animated": "Berkas SVG, nominal $1 × $2 piksel, gedhené berkas: $3",
        "svg-long-error": "Berkas SVG ora sah: $1",
        "show-big-image": "Barkas asli",
-       "show-big-image-preview": "Gedhéning prawuryan iki: $1",
+       "show-big-image-preview": "Gedhéning pratuduh iki: $1",
        "show-big-image-other": "{{PLURAL:$2|Résolusi|Résolusi}} liya: $1.",
        "show-big-image-size": "$1 × $2 piksel",
        "file-info-gif-looped": "mubeng",
index ac51f0f..aad4c59 100644 (file)
        "password-change-forbidden": "이 위키에서 비밀번호를 바꿀 수 없습니다.",
        "externaldberror": "인증 데이터베이스에 오류가 있거나 바깥 계정을 새로 고칠 권한이 없습니다.",
        "login": "로그인",
+       "login-security": "사용자 정보 확인",
        "nav-login-createaccount": "로그인 / 계정 만들기",
        "userlogin": "로그인 / 계정 만들기",
        "userloginnocreate": "로그인",
        "userlogin-resetpassword-link": "비밀번호를 잊으셨나요?",
        "userlogin-helplink2": "로그인에 대한 도움말",
        "userlogin-loggedin": "이미 {{GENDER:$1|$1}} 사용자로 로그인되어 있습니다.\n다른 사용자로 로그인하려면 아래의 양식을 사용하세요.",
+       "userlogin-reauth": "사용자가 $1임을 확인하려면 다시 로그인해야 합니다.",
        "userlogin-createanother": "다른 계정 만들기",
        "createacct-emailrequired": "이메일 주소",
        "createacct-emailoptional": "이메일 주소 (선택 사항)",
        "noname": "사용자 계정 이름이 올바르지 않습니다.",
        "loginsuccesstitle": "로그인함",
        "loginsuccess": "<strong>{{SITENAME}}에 \"$1\" 계정으로 로그인했습니다.</strong>",
-       "nosuchuser": "이름이 \"$1\"인 사용자는 없습니다.\n사용자 계정 이름은 대소문자를 구별합니다.\n철자가 맞는지 확인해주세요. [[Special:CreateAccount|새 계정을 만들 수도 있습니다]].",
+       "nosuchuser": "이름이 \"$1\"인 사용자는 없습니다.\n사용자 이름은 대소문자를 구별합니다.\n철자가 맞는지 확인해주세요. [[Special:CreateAccount|새 계정을 만들 수도 있습니다]].",
        "nosuchusershort": "이름이 \"$1\"인 사용자는 없습니다.\n철자가 맞는지 확인하세요.",
        "nouserspecified": "사용자 계정 이름을 입력하지 않았습니다.",
        "login-userblocked": "이 사용자는 차단되었습니다. 로그인할 수 없습니다.",
        "createacct-another-realname-tip": "실명은 선택 사항입니다.\n실명을 입력하면 문서 기여에 사용자의 이름이 들어가게 됩니다.",
        "pt-login": "로그인",
        "pt-login-button": "로그인",
+       "pt-login-continue-button": "로그인 계속",
        "pt-createaccount": "계정 만들기",
        "pt-userlogout": "로그아웃",
        "php-mail-error-unknown": "PHP의 mail() 함수에서 알 수 없는 오류가 발생했습니다.",
        "botpasswords-invalid-name": "지정된 사용자 이름은 봇 비밀번호 구분자(\"$1\")를 포함하고 있지 않습니다.",
        "botpasswords-not-exist": "\"$1\" 사용자가 이름이 \"$2\"인 봇의 비밀번호를 가지고 있지 않습니다.",
        "resetpass_forbidden": "비밀번호를 바꿀 수 없습니다",
+       "resetpass_forbidden-reason": "암호를 변경할 수 없습니다: $1",
        "resetpass-no-info": "이 페이지에 직접 접근하려면 로그인해야 합니다.",
        "resetpass-submit-loggedin": "비밀번호 바꾸기",
        "resetpass-submit-cancel": "취소",
        "passwordreset-emailsentusername": "이 사용자 이름과 연결된 이메일 주소가 있다면 비밀번호 초기화 이메일이 전송됩니다.",
        "passwordreset-emailsent-capture": "비밀번호 재설정 이메일이 발송되었으며, 아래에 나타나 있습니다.",
        "passwordreset-emailerror-capture": "비밀번호 재설정 이메일이 생성되어 아래에 나타나 있지만, {{GENDER:$2|사용자}}에게 발송하는 데에는 실패했습니다: $1",
+       "passwordreset-invalideamil": "잘못된 이메일 주소",
+       "passwordreset-nodata": "사용자 이름이나 이메일 주소가 지정되지 않았습니다",
        "changeemail": "이메일 주소를 바꾸거나 제거하기",
        "changeemail-header": "이메일 주소를 바꾸려면 이 양식을 채우세요. 계정에서 이메일 연동을 취소하고 싶다면 양식을 제출할 때 새 이메일 주소를 공란으로 두세요.",
        "changeemail-passwordrequired": "변경을 적용하려면 비밀번호를 입력해야 합니다.",
        "log-action-filter-suppress-block": "차단을 통한 사용자 숨기기",
        "log-action-filter-suppress-reblock": "재차단을 통한 사용자 숨기기",
        "log-action-filter-upload-upload": "새로 업로드",
-       "log-action-filter-upload-overwrite": "다시 업로드"
+       "log-action-filter-upload-overwrite": "다시 업로드",
+       "authmanager-authn-not-in-progress": "인증이 진행 중이 아니거나 세션 데이터를 분실했습니다. 처음부터 다시 시작해 주십시오.",
+       "authmanager-authplugin-setpass-failed-title": "비밀번호 변경 실패",
+       "authmanager-authplugin-setpass-failed-message": "인증 플러그인이 비밀번호 변경을 거부했습니다.",
+       "authmanager-authplugin-create-fail": "인증 플러그인이 계정 만들기를 거부했습니다.",
+       "authmanager-authplugin-setpass-denied": "인증 플러그인이 비밀번호 변경을 허용하지 않습니다.",
+       "authmanager-authplugin-setpass-bad-domain": "잘못된 도메인.",
+       "authmanager-autocreate-noperm": "자동 계정 만들기는 허용되지 않습니다.",
+       "authmanager-password-help": "인증을 위한 비밀번호",
+       "authmanager-domain-help": "외부 인증의 도메인",
+       "authmanager-email-label": "이메일",
+       "authmanager-email-help": "이메일 주소",
+       "authmanager-realname-label": "실명",
+       "authmanager-realname-help": "사용자의 실명",
+       "authmanager-provider-password": "비밀번호 기반 인증",
+       "authmanager-provider-temporarypassword": "임시 비밀번호",
+       "authform-wrongtoken": "잘못된 토큰",
+       "authpage-cannot-login": "로그인을 시작할 수 없습니다.",
+       "authpage-cannot-login-continue": "로그인을 계속할 수 없습니다. 사용자 세션의 시간이 초과되었을 가능성이 높습니다.",
+       "authpage-cannot-create": "계정 만들기를 시작할 수 없습니다.",
+       "authpage-cannot-create-continue": "계정 만들기를 계속할 수 없습니다. 사용자 세션의 시간이 초과되었을 가능성이 높습니다.",
+       "authpage-cannot-link": "계정 연결을 시작할 수 없습니다.",
+       "authpage-cannot-link-continue": "계정 연결을 계속할 수 없습니다. 사용자 세션의 시간이 초과되었을 가능성이 높습니다.",
+       "changecredentials-submit-cancel": "취소",
+       "removecredentials-submit": "제거",
+       "removecredentials-submit-cancel": "취소"
 }
index 3b56b14..7aa422d 100644 (file)
        "createacct-reason-ph": "Dlaczego zakładasz kolejne konto",
        "createacct-submit": "Utwórz konto",
        "createacct-another-submit": "Utwórz konto",
+       "createacct-another-continue-submit": "Kontynuuj tworzenie konta",
        "createacct-benefit-heading": "{{grammar:B.lp|{{SITENAME}}}} tworzą ludzie tacy jak Ty.",
        "createacct-benefit-body1": "{{PLURAL:$1|edycja|edycje|edycji}}",
        "createacct-benefit-body2": "{{PLURAL:$1|strona|strony|stron}}",
        "passwordreset-emailsentusername": "Jeśli z tym kontem powiązany jest adres e‐mail, zostanie na niego wysłany e-mail do odzyskiwania hasła.",
        "passwordreset-emailsent-capture": "Wyświetlony poniżej e‐mail pozwalający na zresetowanie hasła został wysłany.",
        "passwordreset-emailerror-capture": "Poniżej wyświetlony e‐mail pozwalający na zresetowanie hasła został wygenerowany, ale nie udało się wysłać go do {{GENDER:$2|użytkownika|użytkowniczki}}: $1",
+       "passwordreset-invalideamil": "Nieprawidłowy adres e-mail",
+       "passwordreset-nodata": "Nie podano ani nazwy użytkownika, ani adresu e-mail",
        "changeemail": "Zmiana lub usunięcie adresu e‐mail",
        "changeemail-header": "Wypełnij ten formularz, aby zmienić swój adres e-mail. Jeśli chcesz usunąć swój adres e-mail, to przy wypełnianiu formularza pozostaw puste pole nowego adresu e-mail.",
        "changeemail-passwordrequired": "Musisz podać swoje hasło, aby potwierdzić tę zmianę.",
        "log-action-filter-rights-autopromote": "Automatyczna zmiana",
        "log-action-filter-upload-upload": "Nowe przesłane",
        "log-action-filter-upload-overwrite": "Przesłane ponownie",
+       "authmanager-create-disabled": "Utworzenie konta jest wyłączone.",
+       "authmanager-create-from-login": "Aby utworzyć konto, wypełnij poniższe pola.",
        "authmanager-authplugin-setpass-denied": "Wtyczka uwierzytelniania nie zezwala na zmianę haseł.",
+       "authmanager-authplugin-setpass-bad-domain": "Niepoprawna domena.",
+       "authmanager-autocreate-noperm": "Automatyczne tworzenie konta jest niedozwolone.",
+       "authmanager-autocreate-exception": "Automatyczne tworzenie konta tymczasowo wyłączone z powodu wcześniejszych błędów.",
        "authmanager-userdoesnotexist": "Konto użytkownika „$1” nie jest zarejestrowane.",
        "authmanager-password-help": "Hasło do uwierzytelniania.",
        "authmanager-email-label": "E-mail",
        "authmanager-email-help": "Adres e‐mail",
        "authmanager-realname-help": "Prawdziwe imię i nazwisko użytkownika",
+       "authmanager-provider-password": "Uwierzytelnianie oparte na haśle",
+       "authmanager-provider-temporarypassword": "Hasło tymczasowe",
        "authprovider-resetpass-skip-label": "Pomiń",
+       "authform-newtoken": "Brakujący token. $1",
        "authform-notoken": "Brakujący token",
        "authform-wrongtoken": "Nieprawidłowy token",
        "specialpage-securitylevel-not-allowed": "Niestety, nie możesz korzystać z tej strony, ponieważ twoja tożsamość nie może zostać zweryfikowana.",
        "authpage-cannot-login-continue": "Nie można kontynuować logowania. Sesja najprawdopodobniej wygasła.",
        "cannotauth-not-allowed-title": "Brak dostępu",
+       "changecredentials-submit-cancel": "Anuluj",
        "removecredentials-submit": "Usuń",
        "removecredentials-submit-cancel": "Anuluj",
        "credentialsform-account": "Nazwa konta:"
index 68b47a3..52c8279 100644 (file)
        "accmailtext": "Na ciav generà a l'ancàpit për [[User talk:$1|$1]] a l'é stàita mandà a $2.\nA peul esse modificà an sla pàgina ''[[Special:ChangePassword|modìfica dla ciav]]'' apress esse rintrà ant ël sistema.",
        "newarticle": "(Neuv)",
        "newarticletext": "A l'é andaje dapress a na liura a na pàgina che a esist ancor nen.\nPër creé la pàgina, ch'a ancamin-a a scrive ant lë spassi sì-sota (vëdde la [$1 pàgina d'agiut] për savèjne ëd pì).\nS'a l'é rivà sì për eror, ch'a sgnaca ël boton '''andaré''' ëd sò navigador.",
-       "anontalkpagetext": "----''Costa a l'é la pàgina ëd ciaciarade për n'utent anònim che a l'é ancó pa dorbusse un cont, ò pura che a lo deuvra nen. Alora i l'oma da dovré ël nùmer d'adrëssa IP për deje n'identificassion a chiel o chila.\nN'adrëssa IP përparèj a peul esse partagià da vàire utent.\nSe chiel a l'é n'utent anònim e a l'ha l'impression d'arsèive dij coment sensa sust, për piasì [[Special:CreateAccount|ch'a crea un cont]] o [[Special:UserLogin|ch'a rintra ant ël sistema]] për evité dë fé confusion con d'àutri utent anònim.''",
+       "anontalkpagetext": "----\n<em>Costa a l'é la pàgina ëd ciaciarade për n'utent anònim che a l'é ancó pa duvertasse un cont, ò pura che a lo deuvra nen.</em>\nAlora i l'oma da dovré ël nùmer d'adrëssa IP për deje n'identificassion a chiel o chila.\nN'adrëssa IP përparèj a peul esse partagià da vàire utent.\nSe chiel a l'é n'utent anònim e a l'ha l'impression d'arsèive dij coment sensa sust, për piasì [[Special:CreateAccount|ch'a crea un cont]] o [[Special:UserLogin|ch'a rintra ant ël sistema]] për evité dë fé confusion con d'àutri utent anònim.''",
        "noarticletext": "Al moment costa pàgina a l'é veuida.\nA peul [[Special:Search/{{PAGENAME}}|sërché cost tìtol]] andrinta a d'àutre pàgine, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sërché ant ij registr colegà],\no purament [{{fullurl:{{FULLPAGENAME}}|action=edit}} modìfiché sta pàgina]</span>.",
        "noarticletext-nopermission": "Al moment a-i é gnun test ansima a costa pàgina.\nA peul [[Special:Search/{{PAGENAME}}|sërché ës tìtol ëd pàgina]] an d'àutre pàgine,\no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sërché ant j'argistr colegà]</span>, ma a l'ha pa ël përmess ëd creé costa pàgina.",
        "missing-revision": "La revision nùmer $1 dla pàgina antitolà «{{FULLPAGENAME}}» a esist pa.\n\nSòn a l'é normalment causà da l'andèje dapress a na vej liura stòrica a na pàgina ch'a l'é stàita scancelà. Ij detaj a peulo esse trovà ant ël [registr ëd jë scancelament ëd {{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}}].",
index 6aff461..f6d2f41 100644 (file)
        "ipb-blocklist": "Used as link text in [[Special:Block]].\n\nThe link points to Specil:BlockList.",
        "ipb-blocklist-contribs": "Used in [[Special:Block]].\n* $1 - target username",
        "ipb-blocklist-duration-left": "Used on [[Special:BlockList]] to show the remaining time (years, months, days, hours, minutes) until the block expires.\n$1 - The duration left",
-       "unblockip": "Used as legend for the form in [[Special:Unblock]].",
+       "unblockip": "Used as title and legend for the form in [[Special:Unblock]].",
        "unblockiptext": "Used in the {{msg-mw|Unblockip}} form on [[Special:Unblock]].",
        "ipusubmit": "Used as button text on [{{canonicalurl:Special:BlockList|action=unblock}} Special:BlockList?action=unblock]. To see the message:\n* Go to [[Special:BlockList]]\n* Click \"unblock\" for any block (but you can only see \"unblock\" if you have administrator rights)\n* It is now the button below the form",
        "unblocked": "{{doc-important|Do not translate the namespace \"User:\".}}\nParameters:\n* $1 - the username that was unblocked\nSee also:\n* {{msg-mw|Unblocked-range}}\n* {{msg-mw|Unblocked-id}}\n*{{msg-mw|Unblocked-ip}}",
        "authmanager-password-help": "Description of the field with label {{msg-mw|userlogin-yourpassword}}.",
        "authmanager-domain-help": "Description of the field with label {{msg-mw|yourdomainname}}.",
        "authmanager-retype-help": "Description of the field with label {{msg-mw|createacct-yourpasswordagain}}.",
-       "authmanager-email-label": "Label for the email field.",
-       "authmanager-email-help": "Description of the field with label {{msg-mw|authmanager-email-label}}.",
-       "authmanager-realname-label": "Label for the realname field.",
+       "authmanager-email-label": "Label for the email field.\n{{Identical|E-mail}}",
+       "authmanager-email-help": "Description of the field with label {{msg-mw|authmanager-email-label}}.\n{{Identical|E-mail address}}",
+       "authmanager-realname-label": "Label for the realname field.\n{{Identical|Real name}}",
        "authmanager-realname-help": "Description of the field with label {{msg-mw|authmanager-realname-label}}.",
        "authmanager-provider-password": "Description for PasswordAuthenticationRequest. Will be used as $1 in messages such as {{msg-mw|authprovider-confirmlink-option}}.",
        "authmanager-provider-password-domain": "Description for PasswordDomainAuthenticationRequest. Will be used as $1 in messages such as {{msg-mw|authprovider-confirmlink-option}}.",
        "authprovider-confirmlink-failed-line": "Line to display that credentials were not linked successfully. Parameters:\n* $1 - Credentials that failed, formatted with {{msg-mw|authprovider-confirmlink-option}}\n* $2 - Failure message text.\n\nSee also:\n* {{msg-mw|authprovider-confirmlink-failed}}\n* {{msg-mw|authprovider-confirmlink-success-line}}",
        "authprovider-confirmlink-failed": "Used to prefix the list of individual link statuses when some did not succeed. Parameters:\n* $1 - Failure message, or a wikitext bulleted list of failure messages.\n\nSee also:\n* {{msg-mw|authprovider-confirmlink-success-line}}\n* {{msg-mw|authprovider-confirmlink-failed-line}}",
        "authprovider-confirmlink-ok-help": "Description of the \"ok\" field when ConfirmLinkSecondaryAuthenticationProvider needs to display link failure messages to the user.",
-       "authprovider-resetpass-skip-label": "Label for the \"Skip\" button when it's possible to skip resetting a password in ResetPasswordSecondaryAuthenticationProvider.",
+       "authprovider-resetpass-skip-label": "Label for the \"Skip\" button when it's possible to skip resetting a password in ResetPasswordSecondaryAuthenticationProvider.\n{{Identical|Skip}}",
        "authprovider-resetpass-skip-help": "Description of the option to skip resetting a password in ResetPasswordSecondaryAuthenticationProvider.",
        "authform-nosession-login": "Error message shown when the login was successful, but the session could not be reestablished on the next request. $1 is an explanation which depends on what session handler is being used, such as {{msg-mw|sessionprovider-nocookies}}.",
        "authform-nosession-signup": "Error message shown when the account creation was successful, but the session could not be reestablished on the next request. $1 is an explanation which depends on what session handler is being used, such as {{msg-mw|sessionprovider-nocookies}}.",
        "cannotauth-not-allowed-title": "Title of the error page shown when the user tries to use an authentication-related page they should not have access to.",
        "cannotauth-not-allowed": "Text of the error page shown when the user tries t use an authentication-related page they should not have access to.",
        "changecredentials": "Title of the special page [[Special:ChangeCredentials]] which allows changing authentication credentials (such as the password).",
-       "changecredentials-submit": "Used on [[Special:ChangeCredentials]].",
+       "changecredentials-submit": "Used on [[Special:ChangeCredentials]].\n{{Identical|Change}}",
        "changecredentials-submit-cancel": "Used on [[Special:ChangeCredentials]].\n{{Identical|Cancel}}",
        "changecredentials-invalidsubpage": "Error message shown when using [[Special:ChangeCredentials]] with an invalid type.\n\nParameters:\n* $1 - subpage name.",
        "changecredentials-success": "Success message after using [[Special:ChangeCredentials]].",
        "removecredentials": "Title of the special page [[Special:RemoveCredentials]] which allows removing authentication credentials (such as a two-factor token).",
-       "removecredentials-submit": "Used on [[Special:RemoveCredentials]].",
+       "removecredentials-submit": "Used on [[Special:RemoveCredentials]].\n{{Identical|Remove}}",
        "removecredentials-submit-cancel": "Used on [[Special:RemoveCredentials]].\n{{Identical|Cancel}}",
        "removecredentials-invalidsubpage": "Error message shown when using [[Special:RemoveCredentials]] with an invalid type.\n\nParameters:\n* $1 - subpage name.",
        "removecredentials-success": "Success message after using [[Special:RemoveCredentials]].",
index c9cd00b..98bb3ce 100644 (file)
        "createacct-submit": "Создать учётную запись",
        "createacct-another-submit": "Создать учётную запись",
        "createacct-continue-submit": "Продолжить создание учётной записи",
+       "createacct-another-continue-submit": "Продолжить создание учётной записи",
        "createacct-benefit-heading": "{{SITENAME}} — совместный труд таких же людей, как вы.",
        "createacct-benefit-body1": "{{PLURAL:$1|правка|правки|правок}}",
        "createacct-benefit-body2": "{{PLURAL:$1|статья|статьи|статей}}",
        "log-action-filter-suppress-reblock": "Сокрытие пользователя через повторное блокирование",
        "log-action-filter-upload-upload": "Новая загрузка",
        "log-action-filter-upload-overwrite": "Повторно загрузить",
+       "authmanager-authn-autocreate-failed": "Автоматическое создание локальной учётной записи не удалось: $1",
        "authmanager-authplugin-setpass-bad-domain": "Неверный домен.",
+       "authmanager-autocreate-exception": "Автоматическое создание учётной записи временно отключено из-за предыдущих ошибок.",
+       "authmanager-userdoesnotexist": "Не зарегистрировано учётной записи «$1».",
        "authmanager-email-label": "Электронная почта",
        "authmanager-email-help": "Адрес электронной почты",
        "authmanager-realname-label": "Настоящее имя",
index 12e1da8..7b995ee 100644 (file)
        "password-change-forbidden": "您不能在本wiki上更改密码。",
        "externaldberror": "验证数据库出错或您被禁止更新您的外部账号。",
        "login": "登录",
+       "login-security": "证明您的身份",
        "nav-login-createaccount": "登录/创建账户",
        "userlogin": "登录/创建账户",
        "userloginnocreate": "登录",
        "userlogin-resetpassword-link": "忘记密码?",
        "userlogin-helplink2": "登录帮助",
        "userlogin-loggedin": "您已经以{{GENDER:$1|$1}}的身份登录。使用下面的表格以其他用户的身份登录。",
+       "userlogin-reauth": "您必须再次登录以证明您是{{GENDER:$1|$1}}。",
        "userlogin-createanother": "创建另一个账户",
        "createacct-emailrequired": "电子邮件地址",
        "createacct-emailoptional": "电子邮件地址(可选)",
        "createacct-email-ph": "请输入你的电子邮件地址",
        "createacct-another-email-ph": "输入电子邮件地址",
        "createaccountmail": "使用一个临时的随机密码并将其发送到指定的电子邮件地址中",
+       "createaccountmail-help": "可被用于为另一个人创建账户而不需要得知密码。",
        "createacct-realname": "真实姓名(可选)",
        "createaccountreason": "原因:",
        "createacct-reason": "原因",
        "createacct-reason-ph": "您为什么要创建另一个账户",
+       "createacct-reason-help": "在账户创建日志中显示的消息",
        "createacct-submit": "创建您的账户",
        "createacct-another-submit": "创建账户",
+       "createacct-continue-submit": "继续账户创建",
+       "createacct-another-continue-submit": "继续账户创建",
        "createacct-benefit-heading": "{{SITENAME}}是由同你一样的人们构筑的。",
        "createacct-benefit-body1": "{{PLURAL:$1|编辑}}",
        "createacct-benefit-body2": "{{PLURAL:$1|页面}}",
        "nocookiesnew": "该用户帐户已被创建,但登录失败。{{SITENAME}}使用Cookie实现用户登录。您已禁用Cookie,请启用Cookie,然后使用您的新用户名与密码登录。",
        "nocookieslogin": "{{SITENAME}}使用Cookie实现用户登录。您已停用Cookie。请启用Cookie后再试。",
        "nocookiesfornew": "该用户账户未被创建,我们不能确认它的来源。请确保你已启用Cookie,刷新本页后再试。",
+       "createacct-loginerror": "账户已成功创建,但您不能自动登录。请继续[[Special:UserLogin|手动登录]]。",
        "noname": "未指定有效的用户名。",
        "loginsuccesstitle": "已登录",
        "loginsuccess": "<strong>您现在已经以\"$1\"的身份登录了{{SITENAME}}。</strong>",
-       "nosuchuser": "没有名为“$1”的用户。用户名区分大小写。请检查的拼写或[[Special:CreateAccount|创建新账户]]。",
+       "nosuchuser": "没有名为“$1”的用户。用户名区分大小写。请检查的拼写或[[Special:CreateAccount|创建新账户]]。",
        "nosuchusershort": "没有名为“$1”的用户。请检查您的拼写。",
        "nouserspecified": "您必须指定一个用户名。",
        "login-userblocked": "该用户已被封禁,禁止登录。",
        "createacct-another-realname-tip": "真实姓名是选填项目。\n如果你选择提供它,它将会用于贡献署名。",
        "pt-login": "登录",
        "pt-login-button": "登录",
+       "pt-login-continue-button": "继续登录",
        "pt-createaccount": "创建账户",
        "pt-userlogout": "退出",
        "php-mail-error-unknown": "在 PHP 的 mail() 函数中的未知错误",
        "botpasswords-invalid-name": "指定的用户名不包含机器人密码分隔符(“$1”)。",
        "botpasswords-not-exist": "用户“$1”没有名叫“$2”的机器人密码。",
        "resetpass_forbidden": "无法更改密码",
+       "resetpass_forbidden-reason": "密码不能更改:$1",
        "resetpass-no-info": "您必须登录后直接进入这个页面。",
        "resetpass-submit-loggedin": "更改密码",
        "resetpass-submit-cancel": "取消",
        "passwordreset-emailsentusername": "如果有邮件地址与此用户名相关联的话,将发送一封密码重置邮件。",
        "passwordreset-emailsent-capture": "密码重设电子邮件已发送,并在下面显示。",
        "passwordreset-emailerror-capture": "重置密码邮件已生成,但是无法向{{GENDER:$2|下列用户}} 发送:$1",
+       "passwordreset-emailsent-capture2": "密码重置{{PLURAL:$1|邮件}}已发送。{{PLURAL:$1|用户名和密码|用户名和密码列表}}在下方显示。",
+       "passwordreset-emailerror-capture2": "向{{GENDER:$2|用户}}发送电子邮件失败:$1 {{PLURAL:$3|用户名和密码|用户名和密码列表}}在下方显示。",
+       "passwordreset-nocaller": "必须提供一个调用方",
+       "passwordreset-nosuchcaller": "调用方不存在:$1",
+       "passwordreset-ignored": "密码重置没有处理。也许没有配置提供者?",
+       "passwordreset-invalideamil": "无效的电子邮件地址",
+       "passwordreset-nodata": "用户名和电子邮件地址均未提供",
        "changeemail": "更改或移除电子邮件地址",
        "changeemail-header": "完成此表格以更改您的电子邮件地址。如果您希望从您的账户中移除任何关联的电子邮件地址,请在提交表格时将新电子邮件地址留空。",
        "changeemail-passwordrequired": "您需要输入您的密码以确认此次更改。",
        "log-action-filter-suppress-block": "通过封禁的用户屏蔽",
        "log-action-filter-suppress-reblock": "通过再封禁的用户屏蔽",
        "log-action-filter-upload-upload": "新上传",
-       "log-action-filter-upload-overwrite": "重新上传"
+       "log-action-filter-upload-overwrite": "重新上传",
+       "authmanager-authn-not-in-progress": "身份验证尚未进行,或会话数据丢失。请从头重新开始。",
+       "authmanager-authn-no-primary": "提供的证书不能被验证。",
+       "authmanager-authn-no-local-user": "提供的证书没有与该wiki上的任何用户相关联。",
+       "authmanager-authn-no-local-user-link": "提供的证书有效,但没有与该wiki上的任何用户相关联。请通过不同方式登录,或创建一个新用户,然后您将拥有一个把您之前的证书链接到对应账户的选项。",
+       "authmanager-authn-autocreate-failed": "所有账户的自动创建失败:$1",
+       "authmanager-change-not-supported": "提供的证书不能被更改,因为没有东西会使用它们。",
+       "authmanager-create-disabled": "账户创建已停用。",
+       "authmanager-create-from-login": "要创建您的账户,请填写下方的字段。",
+       "authmanager-create-not-in-progress": "账户创建尚未进行,或会话数据丢失。请从头重新开始。",
+       "authmanager-create-no-primary": "提供的证书不能用于账户创建。",
+       "authmanager-link-no-primary": "提供的证书不能用于账户链接。",
+       "authmanager-link-not-in-progress": "账户链接尚未进行,或会话数据丢失。请从头重新开始。",
+       "authmanager-authplugin-setpass-failed-title": "密码更改失败",
+       "authmanager-authplugin-setpass-failed-message": "身份验证插件拒绝了密码更改。",
+       "authmanager-authplugin-create-fail": "身份验证插件拒绝了账户创建。",
+       "authmanager-authplugin-setpass-denied": "身份验证插件不允许更改密码。",
+       "authmanager-authplugin-setpass-bad-domain": "无效域。",
+       "authmanager-autocreate-noperm": "不允许自动账户创建。",
+       "authmanager-autocreate-exception": "由于之前的错误,自动账户创建已临时停用。",
+       "authmanager-userdoesnotexist": "用户帐户“$1”尚未注册。",
+       "authmanager-userlogin-remembermypassword-help": "密码是否应为长于会话长度而被记住。",
+       "authmanager-username-help": "用于身份验证的用户名。",
+       "authmanager-password-help": "用于身份验证的密码。",
+       "authmanager-domain-help": "外部身份验证域。",
+       "authmanager-retype-help": "再次输入密码以确认。",
+       "authmanager-email-label": "电子邮件",
+       "authmanager-email-help": "电子邮件地址",
+       "authmanager-realname-label": "真实姓名",
+       "authmanager-realname-help": "用户的真实姓名",
+       "authmanager-provider-password": "基于密码的身份验证",
+       "authmanager-provider-password-domain": "基于密码和域的身份验证",
+       "authmanager-provider-temporarypassword": "临时密码",
+       "authprovider-confirmlink-message": "基于您最近的登录尝试,以下账户可被链接至您的wiki账户。链接它们会启用通过这些账户的登录。请选择应链接的账户。",
+       "authprovider-confirmlink-request-label": "应被链接的账户",
+       "authprovider-confirmlink-success-line": "$1:已成功连接。",
+       "authprovider-confirmlink-failed": "账户链接未完全成功:$1",
+       "authprovider-confirmlink-ok-help": "在显示链接失败消息后继续。",
+       "authprovider-resetpass-skip-label": "跳过",
+       "authprovider-resetpass-skip-help": "跳过重置密码。",
+       "authform-nosession-login": "身份验证已成功,但您的浏览器不能“记住”其登录。\n\n$1",
+       "authform-nosession-signup": "账户已创建,但您的浏览器不能“记住”其登录。\n\n$1",
+       "authform-newtoken": "丢失令牌。$1",
+       "authform-notoken": "丢失令牌",
+       "authform-wrongtoken": "错误令牌",
+       "specialpage-securitylevel-not-allowed-title": "不允许",
+       "specialpage-securitylevel-not-allowed": "对不起,您未被允许使用此页面,因为您的身份不能被验证。",
+       "authpage-cannot-login": "无法开始登录。",
+       "authpage-cannot-login-continue": "无法继续登录。您的会话大概已超时。",
+       "authpage-cannot-create": "无法开始账户创建。",
+       "authpage-cannot-create-continue": "无法继续账户创建。您的会话大概已超时。",
+       "authpage-cannot-link": "无法开始账户链接。",
+       "authpage-cannot-link-continue": "无法继续账户链接。您的会话大概已超时。",
+       "cannotauth-not-allowed-title": "权限被拒绝",
+       "cannotauth-not-allowed": "您不被允许使用此页面",
+       "changecredentials": "更改证书",
+       "changecredentials-submit": "更改",
+       "changecredentials-submit-cancel": "取消",
+       "changecredentials-invalidsubpage": "$1不是有效的证书类型。",
+       "changecredentials-success": "您的证书已被更改。",
+       "removecredentials": "移除证书",
+       "removecredentials-submit": "移除",
+       "removecredentials-submit-cancel": "取消",
+       "removecredentials-invalidsubpage": "$1不是有效的证书类型。",
+       "removecredentials-success": "您的证书已被移除。",
+       "credentialsform-provider": "证书类型:",
+       "credentialsform-account": "帐户名称:",
+       "cannotlink-no-provider-title": "没有可链接账户",
+       "cannotlink-no-provider": "没有可链接账户。",
+       "linkaccounts": "链接账户",
+       "linkaccounts-success-text": "账户已链接。",
+       "linkaccounts-submit": "链接帐户",
+       "unlinkaccounts": "取消链接账户",
+       "unlinkaccounts-success": "账户已取消链接。"
 }
index 2f414df..292a25d 100644 (file)
@@ -59,7 +59,7 @@ class RecompressTracked {
        public $reportingInterval = 10;
        public $numProcs = 1;
        public $numBatches = 0;
-       public $useDiff, $pageBlobClass, $orphanBlobClass;
+       public $pageBlobClass, $orphanBlobClass;
        public $slavePipes, $slaveProcs, $prevSlaveId;
        public $copyOnly = false;
        public $isChild = false;
@@ -112,8 +112,8 @@ class RecompressTracked {
                } elseif ( $this->slaveId !== false ) {
                        $GLOBALS['wgDebugLogPrefix'] = "RCT {$this->slaveId}: ";
                }
-               $this->useDiff = function_exists( 'xdiff_string_bdiff' );
-               $this->pageBlobClass = $this->useDiff ? 'DiffHistoryBlob' : 'ConcatenatedGzipHistoryBlob';
+               $this->pageBlobClass = function_exists( 'xdiff_string_bdiff' ) ?
+                       'DiffHistoryBlob' : 'ConcatenatedGzipHistoryBlob';
                $this->orphanBlobClass = 'ConcatenatedGzipHistoryBlob';
        }
 
index ecd10fa..f9c6117 100644 (file)
@@ -5,12 +5,12 @@
 body {
        margin: 0;
        background: #eee;
-       font-family: Verdana;
+       font-family: 'Verdana';
        color: #333;
 }
 
 #main {
-       border: 1px solid #D0D0D0;
+       border: 1px solid #d0d0d0;
        background: #fff;
        margin: 0.5em;
 }
@@ -25,7 +25,7 @@ body {
        border-left: 1px dotted #ccc;
        float: right;
        width: 230px;
-       background: white;
+       background: #fff;
        margin: 0 0 10px 10px;
 }
 
index 0e0b304..66f8578 100644 (file)
 }
 
 .error {
-       color: red;
+       color: #f00;
        background-color: #fff;
        font-weight: bold;
        left: 1em;
 .success-message {
        font-weight: bold;
        font-size: 110%;
-       color: green;
+       color: #0f0;
 }
 
 .success-box {
index 394f36e..fb3815d 100644 (file)
     "grunt-contrib-watch": "1.0.0",
     "grunt-jscs": "2.8.0",
     "grunt-jsonlint": "1.0.7",
-    "grunt-karma": "0.12.2",
+    "grunt-karma": "1.0.0",
+    "grunt-stylelint": "0.2.0",
     "karma": "0.13.22",
-    "karma-chrome-launcher": "0.2.2",
-    "karma-firefox-launcher": "0.1.7",
-    "karma-qunit": "0.1.9",
-    "qunitjs": "1.22.0"
+    "karma-chrome-launcher": "1.0.1",
+    "karma-firefox-launcher": "1.0.0",
+    "karma-qunit": "1.0.0",
+    "qunitjs": "1.22.0",
+    "stylelint-config-wikimedia": "0.1.0"
   }
 }
index 33d9a00..5689256 100644 (file)
@@ -7,9 +7,9 @@
 .tipsy-inner {
        padding: 5px 8px 4px 8px;
        /*background-color: #e8f2f8;*/
-       background-color: #ffffff;
+       background-color: #fff;
        border: solid 1px #a7d7f9;
-       color: black;
+       color: #000;
        max-width: 15em;
        border-radius: 4px;
        /*
index f8f6e95..254836a 100644 (file)
@@ -1,7 +1,7 @@
 .arrowSteps {
        list-style-type: none;
        list-style-image: none;
-       border: 1px solid #666666;
+       border: 1px solid #666;
        position: relative;
 }
 
index 34cdf76..31158f7 100644 (file)
@@ -11,7 +11,7 @@
 
 .mw-badge-content {
        font-weight: bold;
-       color: white;
+       color: #fff;
        vertical-align: baseline;
        text-shadow: 0 1px rgba(0, 0, 0, 0.4);
 }
@@ -32,5 +32,5 @@
 }
 
 .mw-badge-important {
-       background-color: #cc0000;
+       background-color: #c00;
 }
index 15cd926..f6b4fd1 100644 (file)
@@ -14,9 +14,9 @@
 
 .suggestions-special {
        position: relative;
-       background-color: white;
+       background-color: #fff;
        cursor: pointer;
-       border: solid 1px #aaaaaa;
+       border: solid 1px #aaa;
        padding: 0;
        margin: 0;
        margin-top: -2px;
 }
 
 .suggestions-results {
-       background-color: white;
+       background-color: #fff;
        cursor: pointer;
-       border: solid 1px #aaaaaa;
+       border: solid 1px #aaa;
        padding: 0;
        margin: 0;
 }
 
 .suggestions-result {
-       color: black;
+       color: #000;
        margin: 0;
        line-height: 1.5em;
        padding: 0.01em 0.25em;
 }
 
 .suggestions-result-current {
-       background-color: #4C59A6;
-       color: white;
+       background-color: #4c59a6;
+       color: #fff;
 }
 
 .suggestions-special .special-label {
-       color: gray;
+       color: #808080;
        text-align: left;
 }
 
 .suggestions-special .special-query {
-       color: black;
+       color: #000;
        font-style: italic;
        text-align: left;
 }
 
 .suggestions-special .special-hover {
-       background-color: silver;
+       background-color: #c0c0c0;
 }
 
 .suggestions-result-current .special-label,
 .suggestions-result-current .special-query {
-       color: white;
+       color: #fff;
 }
 
 .highlight {
index 603a965..835383e 100644 (file)
@@ -2,3 +2,8 @@
 #pagehistory li.after input[name="diff"] {
        visibility: hidden;
 }
+
+span.updatedmarker {
+       color: #000;
+       background-color: #0f0;
+}
index 0887476..9db6777 100644 (file)
@@ -72,7 +72,7 @@ td.diff-deletedline {
 td.diff-context {
        background: #f9f9f9;
        border-color: #e6e6e6;
-       color: #333333;
+       color: #333;
 }
 
 .diffchange {
index 5112728..450517e 100644 (file)
@@ -38,7 +38,7 @@ table.filehistory td.filehistory-selected {
 .filehistory a img,
 #file img:hover {
        /* @embed */
-       background: white url(images/checker.png) repeat;
+       background: #fff url(images/checker.png) repeat;
 }
 
 /*
@@ -46,7 +46,7 @@ table.filehistory td.filehistory-selected {
  */
 ul#filetoc {
        text-align: center;
-       border: 1px solid #aaaaaa;
+       border: 1px solid #aaa;
        background-color: #f9f9f9;
        padding: 5px;
        font-size: 95%;
index db89990..a02b4b4 100644 (file)
        font-size: 1.25em;
        font-weight: bold;
        line-height: 2.3em;
-       color: black;
-       text-shadow: 0 0.0625em 0 white;
+       color: #000;
+       text-shadow: 0 0.0625em 0 #fff;
        text-decoration: none;
        opacity: 0.2;
        filter: alpha(opacity=20);
 }
 
 .postedit-close:hover {
-       color: black;
+       color: #000;
        text-decoration: none;
        opacity: 0.4;
        filter: alpha(opacity=40);
index 9adfba1..c58bcc8 100644 (file)
@@ -80,7 +80,7 @@ table.rimage {
 div.thumb {
        margin-bottom: .5em;
        border-style: solid;
-       border-color: white;
+       border-color: #fff;
        width: auto;
 }
 
@@ -147,7 +147,7 @@ div.tleft {
 }
 
 img.thumbborder {
-       border: 1px solid #dddddd;
+       border: 1px solid #ddd;
 }
 
 /* Page history styling */
@@ -212,7 +212,7 @@ table.toc td {
 }
 
 .error {
-       color: red;
+       color: #f00;
        font-size: larger;
 }
 
@@ -224,12 +224,12 @@ table.toc td {
 }
 
 #preftoc li {
-       border: 1px solid White;
+       border: 1px solid #fff;
 }
 
 #preftoc li.selected {
        background-color: #f9f9f9;
-       border: 1px dashed #aaaaaa;
+       border: 1px dashed #aaa;
 }
 
 #preftoc a,
@@ -272,14 +272,14 @@ table.small {
 
 /* use this instead of #toc for page content */
 .toccolours {
-       border: 1px solid #aaaaaa;
+       border: 1px solid #aaa;
        background-color: #f9f9f9;
        padding: 5px;
        font-size: 95%;
 }
 
 #siteNotice {
-       border: 1px solid #aaaaaa;
+       border: 1px solid #aaa;
        padding-left: 0.5em;
        padding-right: 0.5em;
 }
@@ -290,12 +290,12 @@ table.small {
 
 span.unpatrolled {
        font-weight: bold;
-       color: red;
+       color: #f00;
 }
 
 span.updatedmarker {
-       color: black;
-       background-color: #00FF00;
+       color: #000;
+       background-color: #0f0;
 }
 
 div.gallerybox {
@@ -308,14 +308,14 @@ span.comment {
 
 .previewnote {
        text-align: center;
-       color: #cc0000;
+       color: #c00;
 }
 
 .editExternally {
        border-style: solid;
        border-width: 1px;
-       border-color: gray;
-       background: #ffffff;
+       border-color: #808080;
+       background: #fff;
        padding: 3px;
        margin-top: 0.5em;
        float: left;
@@ -325,7 +325,7 @@ span.comment {
 
 .editExternallyHelp {
        font-style: italic;
-       color: gray;
+       color: #808080;
 }
 
 li span.deleted {
@@ -358,7 +358,7 @@ table.mw_metadata {
 
 table.mw_metadata td,
 table.mw_metadata th {
-       border: 1px solid #aaaaaa;
+       border: 1px solid #aaa;
        padding-left: 4px;
        padding-right: 4px;
 }
@@ -403,7 +403,7 @@ table.mw_metadata td.spacer {
 }
 
 div.multipageimagenavbox {
-       border: solid 1px silver;
+       border: solid 1px #c0c0c0;
        padding: 4px;
        margin: 1em;
        background: #f0f0f0;
@@ -450,7 +450,7 @@ body {
 }
 
 body.ns-0 {
-       background-color: white;
+       background-color: #fff;
 }
 
 /** RTL specific CSS starts here **/
index ec6cee4..e18ef69 100644 (file)
@@ -44,8 +44,8 @@
 
 /* User-Agent styles for new HTML5 elements */
 mark {
-       background-color: yellow;
-       color: black;
+       background-color: #ff0;
+       color: #000;
 }
 
 /* Helper for wbr element on IE 8+; in HTML5, but not supported by default as of IE 11. */
@@ -156,7 +156,7 @@ span.history-deleted {
 
 .unpatrolled {
        font-weight: bold;
-       color: red;
+       color: #f00;
 }
 
 div.patrollink {
@@ -246,7 +246,7 @@ input#wpSummary {
 .catlinks li {
        display: inline-block;
        line-height: 1.25em;
-       border-left: 1px solid #AAA;
+       border-left: 1px solid #aaa;
        margin: 0.125em 0;
        padding: 0 0.5em;
        zoom: 1;
@@ -288,7 +288,7 @@ p.mw-delete-editreasons {
 
 /* The auto-generated edit comments */
 .autocomment {
-       color: gray;
+       color: #808080;
 }
 
 #pagehistory .history-user {
@@ -297,7 +297,7 @@ p.mw-delete-editreasons {
 }
 
 #pagehistory li {
-       border: 1px solid white;
+       border: 1px solid #fff;
 }
 
 #pagehistory li.selected {
@@ -325,7 +325,7 @@ p.mw-delete-editreasons {
 div.mw-warning-with-logexcerpt {
        padding: 3px;
        margin-bottom: 3px;
-       border: 2px solid #2F6FAB;
+       border: 2px solid #2f6fab;
        clear: both;
 }
 
@@ -352,7 +352,7 @@ th.mw-revdel-checkbox {
 
 /* red links; see bug 36276 */
 a.new {
-       color: #BA0000;
+       color: #ba0000;
 }
 
 /* Plainlinks - this can be used to switch
@@ -378,7 +378,7 @@ table.wikitable {
        background-color: #f9f9f9;
        border: 1px solid #aaa;
        border-collapse: collapse;
-       color: black;
+       color: #000;
 }
 
 table.wikitable > tr > th,
@@ -407,7 +407,7 @@ table.wikitable > caption {
 }
 
 .error {
-       color: #cc0000;
+       color: #c00;
 }
 
 .warning {
@@ -441,7 +441,7 @@ table.wikitable > caption {
 }
 
 .errorbox {
-       color: #cc0000;
+       color: #c00;
        border-color: #fac5c5;
        background-color: #fae3e3;
 }
@@ -504,20 +504,20 @@ table.wikitable > caption {
 .mw-datatable,
 .mw-datatable td,
 .mw-datatable th {
-       border: 1px solid #aaaaaa;
+       border: 1px solid #aaa;
        padding: 0 0.15em 0 0.15em;
 }
 
 .mw-datatable th {
-       background-color: #ddddff;
+       background-color: #ddf;
 }
 
 .mw-datatable td {
-       background-color: #ffffff;
+       background-color: #fff;
 }
 
 .mw-datatable tr:hover td {
-       background-color: #eeeeff;
+       background-color: #eef;
 }
 
 /* Classes for Exif data display */
@@ -548,7 +548,7 @@ table.mw_metadata {
 
 table.mw_metadata td,
 table.mw_metadata th {
-       border: 1px solid #aaaaaa;
+       border: 1px solid #aaa;
        padding-left: 5px;
        padding-right: 5px;
 }
index 89f8745..38f33be 100644 (file)
@@ -198,7 +198,7 @@ div.magnify a {
 }
 
 img.thumbborder {
-       border: 1px solid #dddddd;
+       border: 1px solid #ddd;
 }
 
 /* Directionality-specific styles for thumbnails - their positioning depends on content language */
index 99d465b..74911b7 100644 (file)
@@ -126,7 +126,7 @@ figure[typeof~='mw:Image/Frame'] {
 figure[typeof~='mw:Image/Thumb'] > *:first-child > img,
 figure[typeof~='mw:Image/Frame'] > *:first-child > img,
 .mw-image-border > *:first-child > img {
-       border: 1px solid #cccccc;
+       border: 1px solid #ccc;
        margin: 3px;
 }
 
index 7872085..7b0b071 100644 (file)
@@ -34,7 +34,7 @@ a:lang(ur) {
 }
 
 a.stub {
-       color: #772233;
+       color: #723;
 }
 
 a.new, #p-personal a.new {
@@ -97,7 +97,7 @@ h3,
 h4,
 h5,
 h6 {
-       color: black;
+       color: #000;
        background: none;
        font-weight: normal;
        margin: 0;
@@ -195,11 +195,11 @@ pre, code, tt, kbd, samp, .mw-code {
         * Some browsers will render the monospace text too small, namely Firefox, Chrome and Safari.
         * Specifying any valid, second value will trigger correct behavior without forcing a different font.
         */
-       font-family: monospace, Courier;
+       font-family: monospace, 'Courier';
 }
 
 code {
-       color: black;
+       color: #000;
        background-color: #f9f9f9;
        border: 1px solid #ddd;
        border-radius: 2px;
@@ -208,7 +208,7 @@ code {
 
 pre,
 .mw-code {
-       color: black;
+       color: #000;
        background-color: #f9f9f9;
        border: 1px solid #ddd;
        padding: 1em;
index b57ee36..8b07721 100644 (file)
@@ -16,8 +16,8 @@
 }
 
 .editOptions {
-       background-color: #F0F0F0;
-       border: 1px solid silver;
+       background-color: #f0f0f0;
+       border: 1px solid #c0c0c0;
        border-top: none;
        padding: 1em 1em 1.5em 1em;
        margin-bottom: 2em;
@@ -26,7 +26,7 @@
 .usermessage {
        background-color: #ffce7b;
        border: 1px solid #ffa500;
-       color: black;
+       color: #000;
        font-weight: bold;
        margin: 2em 0 1em;
        padding: .5em 1em;
index 15f4e4d..f2019e7 100644 (file)
@@ -103,7 +103,7 @@ span.mw-protectedpages-actions {
        font-size: 90%;
 }
 span.mw-protectedpages-unknown {
-       color: grey;
+       color: #808080;
        font-size: 90%;
 }
 
index e05d163..4c9c41e 100644 (file)
@@ -3,7 +3,7 @@
 .mw-email-none .mw-input{
        border: 1px solid #fde29b;
        background-color: #fdf1d1;
-       color: #000000;
+       color: #000;
 }
 /* Authenticated email field has its own class too. Unstyled by default */
 /*
old mode 100755 (executable)
new mode 100644 (file)
index 1ce9569..1e99361
@@ -44,13 +44,13 @@ div.searchresult {
        font-size: 108%;
 }
 .mw-search-result-data {
-       color: green;
+       color: #0f0;
        font-size: 97%;
 }
 .mw-search-profile-tabs {
        background-color: #f3f3f3;
        margin-top: 1em;
-       border: 1px solid silver;
+       border: 1px solid #c0c0c0;
 }
 .mw-search-profile-tabs div.search-types {
        float: left;
@@ -71,7 +71,7 @@ div.searchresult {
        padding: 0.5em;
 }
 .mw-search-profile-tabs div.search-types ul li.current a {
-       color: #333333;
+       color: #333;
        cursor: default;
 }
 .mw-search-profile-tabs div.search-types ul li.current a:hover {
@@ -90,7 +90,7 @@ fieldset#mw-searchoptions {
        padding: 0.5em 0.75em 0.75em 0.75em;
        border: none;
        background-color: #f9f9f9;
-       border: 1px solid silver;
+       border: 1px solid #c0c0c0;
        border-top-width: 0;
 }
 fieldset#mw-searchoptions legend {
@@ -121,18 +121,18 @@ fieldset#mw-searchoptions table td {
 }
 fieldset#mw-searchoptions div.divider {
        clear: both;
-       border-bottom: 1px solid #DDDDDD;
+       border-bottom: 1px solid #ddd;
        padding-top: 0.5em;
        margin-bottom: 0.5em;
 }
 td#mw-search-menu {
-       padding-left:6em;
-       font-size:85%;
+       padding-left: 6em;
+       font-size: 85%;
 }
 div#mw-search-interwiki {
        float: right;
        width: 18em;
-       border: 1px solid #AAAAAA;
+       border: 1px solid #aaa;
        margin-top: 2ex;
 }
 div#mw-search-interwiki li {
@@ -152,7 +152,7 @@ div#mw-search-interwiki-caption {
        text-align: left;
        padding: 0.15em 0.15em 0.2em 0.2em;
        background-color: #ececec;
-       border-top: 1px solid #BBBBBB;
+       border-top: 1px solid #bbb;
 }
 span.searchalttitle {
        font-size: 95%;
index 0998d4c..87cdb02 100644 (file)
@@ -45,7 +45,7 @@ div.mw-createacct-benefits-container h2 {
        margin: 0;
        padding: 0;
        color: #252525;
-       font-family: "Linux Libertine", Georgia, Times, serif;
+       font-family: 'Linux Libertine', 'Georgia', 'Times', serif;
        font-weight: normal;
        font-size: 2.2em;
        line-height: 1.2;
index a7beb0d..74c75ea 100644 (file)
                }
        }
 
-       background-color: white;
+       background-color: #fff;
        border: 1px solid #ccc;
 
        &.mw-widgets-datetime-calendarWidget-dependent {
                margin-top: -1px;
-               border-top: 1px solid white;
+               border-top: 1px solid #fff;
        }
 
        &-heading {
index bc387df..84788d2 100644 (file)
@@ -51,7 +51,7 @@
                padding: 0 1em;
                margin: 0;
                background-color: #fff;
-               color: black;
+               color: #000;
                border: solid 1px #ccc;
                box-shadow: inset 0 0 0 0 @progressive;
                border-radius: 0.1em;
 
                &.oo-ui-flaggedElement-invalid {
                        .mw-widgets-datetime-dateTimeInputWidget-handle {
-                               border-color: red;
-                               box-shadow: inset 0 0 0 0 red;
+                               border-color: #f00;
+                               box-shadow: inset 0 0 0 0 #f00;
                        }
 
                        .mw-widgets-datetime-dateTimeInputWidget-handle:focus {
-                               border-color: red;
-                               box-shadow: inset 0 0 0 0.1em red;
+                               border-color: #f00;
+                               box-shadow: inset 0 0 0 0.1em #f00;
                        }
                }
        }
        }
 
        &-editField.mw-widgets-datetime-dateTimeInputWidget-invalid {
-               border: 1px solid red;
-               box-shadow: inset 0 0 0 0 red;
+               border: 1px solid #f00;
+               box-shadow: inset 0 0 0 0 #f00;
 
                &:focus {
-                       border: 1px solid red;
-                       box-shadow: inset 0 0 0 0.1em red;
+                       border: 1px solid #f00;
+                       box-shadow: inset 0 0 0 0.1em #f00;
                }
        }
 
index 873cca1..ee571cb 100644 (file)
@@ -74,7 +74,7 @@
                border: 1px solid #ccc;
                border-radius: 0.1em;
                line-height: 1.275em;
-               background-color: white;
+               background-color: #fff;
        }
 
        &.oo-ui-indicatorElement .mw-widget-dateInputWidget-handle > .oo-ui-indicatorElement-indicator {
@@ -91,7 +91,7 @@
        }
 
        &-calendar {
-               background-color: white;
+               background-color: #fff;
                margin-top: -2px;
 
                &:focus {
 
        &.oo-ui-flaggedElement-invalid {
                .mw-widget-dateInputWidget-handle {
-                       border-color: red;
-                       box-shadow: inset 0 0 0 0 red;
+                       border-color: #f00;
+                       box-shadow: inset 0 0 0 0 #f00;
                }
        }
 
index cf9496f..ecfc880 100644 (file)
@@ -20,8 +20,8 @@
 
 .mw-widgets-stashedFileWidget-info {
        height: 2.4em;
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
+       background-color: #fff;
+       border: 1px solid #ccc;
        border-radius: 2px;
        width: 100%;
        display: table-cell;
@@ -51,7 +51,7 @@
                        float: left;
                }
                > .mw-widgets-stashedFileWidget-fileType {
-                       color: #888888;
+                       color: #888;
                        float: right;
                }
        }
@@ -79,9 +79,9 @@
 
        &.oo-ui-widget-disabled {
                .mw-widgets-stashedFileWidget-info {
-                       color: #cccccc;
-                       text-shadow: 0 1px 1px #ffffff;
-                       border-color: #dddddd;
+                       color: #ccc;
+                       text-shadow: 0 1px 1px #fff;
+                       border-color: #ddd;
                        background-color: #f3f3f3;
 
                        > .oo-ui-iconElement-icon,
@@ -97,8 +97,8 @@
        height: 5.5em;
        text-align: left;
        padding: 0;
-       background-color: #ffffff;
-       border: 1px solid #cccccc;
+       background-color: #fff;
+       border: 1px solid #ccc;
        margin-bottom: 0.5em;
        vertical-align: middle;
        overflow: hidden;
 
                > .mw-widgets-stashedFileWidget-noThumbnail-icon {
                        opacity: 0.4;
-                       background-color: #cccccc;
+                       background-color: #ccc;
                        height: 5.5em;
                        width: 5.5em;
                }
        }
 
        .mw-widgets-stashedFileWidget-label {
-               color: #cccccc;
+               color: #ccc;
                right: 0.5em;
        }
 
index a35ce7a..365e988 100644 (file)
@@ -28,7 +28,7 @@ div.apihelp-linktrail {
 .apihelp-flags {
        font-size: smaller;
        float: right;
-       border: 1px solid black;
+       border: 1px solid #000;
        padding: 0.25em;
        width: 20em;
 }
@@ -36,7 +36,7 @@ div.apihelp-linktrail {
 .apihelp-deprecated, .apihelp-flag-deprecated,
 .apihelp-flag-internal strong {
        font-weight: bold;
-       color: red;
+       color: #f00;
 }
 
 .apihelp-unknown {
index 9e20264..91fa02a 100644 (file)
@@ -13,7 +13,7 @@
 
 .mw-json th,
 .mw-json td {
-       border: 1px solid gray;
+       border: 1px solid #808080;
        font-size: 16px;
        padding: 0.5em 1em;
 }
@@ -50,7 +50,7 @@
 }
 
 .mw-json table caption {
-       color: gray;
+       color: #808080;
        display: inline-block;
        font-size: 10px;
        font-style: italic;
index 949c558..54620f4 100644 (file)
@@ -65,7 +65,7 @@
        font-size: 13px;
        /* IE-hack for display: inline-block */
        zoom: 1;
-       *display:inline;
+       *display: inline;
 }
 
 .mw-debug-panelink {
@@ -115,7 +115,7 @@ a.mw-debug-panelabel:visited {
        th,
        td,
        table {
-               border: 1px solid #D0DBB3;
+               border: 1px solid #d0dbb3;
                border-collapse: collapse;
                margin: 0;
        }
@@ -127,12 +127,12 @@ a.mw-debug-panelabel:visited {
        }
 
        th {
-               background-color: #F1F7E2;
+               background-color: #f1f7e2;
                font-weight: bold;
        }
 
        td {
-               background-color: white;
+               background-color: #fff;
        }
 }
 
index f4af4ba..89efae3 100644 (file)
@@ -9,7 +9,7 @@
        }
 
        .mediawiki-filewarning-footer {
-               color: #888888;
+               color: #888;
        }
 
        .empty {
index c3341bb..9405f6b 100644 (file)
@@ -5,7 +5,7 @@ table.mw-htmlform-nolabel td.mw-label {
 }
 
 .mw-htmlform-invalid-input td.mw-input input {
-       border-color: red;
+       border-color: #f00;
 }
 
 .mw-htmlform-flatlist div.mw-htmlform-flatlist-item {
index 188af6a..5111d96 100644 (file)
 }
 
 .mw-notification-type-warn {
-       border-color: #F5BE00; /* yellow */
-       background-color: #FFFFE8;
+       border-color: #f5be00; /* yellow */
+       background-color: #ffffe8;
 }
 
 .mw-notification-type-error {
-       border-color: #EB3941; /* red */
-       background-color: #FFF8F8;
+       border-color: #eb3941; /* red */
+       background-color: #fff8f8;
 }
index df144ce..ce3cfbd 100644 (file)
@@ -4,7 +4,7 @@
 .suggestions a.mw-searchSuggest-link:hover,
 .suggestions a.mw-searchSuggest-link:active,
 .suggestions a.mw-searchSuggest-link:focus {
-       color: black;
+       color: #000;
        text-decoration: none;
 }
 
@@ -12,7 +12,7 @@
 .suggestions-result-current a.mw-searchSuggest-link:hover,
 .suggestions-result-current a.mw-searchSuggest-link:active,
 .suggestions-result-current a.mw-searchSuggest-link:focus {
-       color: white;
+       color: #fff;
 }
 
 .suggestions a.mw-searchSuggest-link .special-query {
index 3c80bbb..4d43e6a 100644 (file)
@@ -84,7 +84,7 @@ ul.mw-gallery-packed-hover li.gallerybox:hover div.gallerytextwrapper,
 ul.mw-gallery-packed-overlay li.gallerybox div.gallerytextwrapper,
 ul.mw-gallery-packed-hover li.gallerybox.mw-gallery-focused div.gallerytextwrapper {
        position: absolute;
-       background: white;
+       background: #fff;
        background: rgba(255, 255, 255, 0.8);
        padding: 5px 10px;
        bottom: 0;
index ade6914..0a5d3cd 100644 (file)
@@ -1,4 +1,4 @@
-@chrome @firefox @vagrant
+@chrome @firefox @skip @vagrant
 Feature: Edit Page
 
   Scenario: Create and edit page
index 95136d2..8e72bcf 100644 (file)
@@ -1,4 +1,4 @@
-@chrome @firefox @vagrant
+@chrome @firefox @skip @vagrant
 Feature: View History
 
   Scenario: Edit page and view history
index c242923..9672cdc 100644 (file)
@@ -600,6 +600,29 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                }
        }
 
+       /**
+        * Check if we can back up a value by performing a shallow copy.
+        * Values which fail this test are copied recursively.
+        *
+        * @param mixed $value
+        * @return bool True if a shallow copy will do; false if a deep copy
+        *  is required.
+        */
+       private static function canShallowCopy( $value ) {
+               if ( is_scalar( $value ) || $value === null ) {
+                       return true;
+               }
+               if ( is_array( $value ) ) {
+                       foreach ( $value as $subValue ) {
+                               if ( !is_scalar( $subValue ) && $subValue !== null ) {
+                                       return false;
+                               }
+                       }
+                       return true;
+               }
+               return false;
+       }
+
        /**
         * Stashes the global, will be restored in tearDown()
         *
@@ -635,13 +658,22 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
                                // NOTE: we serialize then unserialize the value in case it is an object
                                // this stops any objects being passed by reference. We could use clone
                                // and if is_object but this does account for objects within objects!
-                               try {
-                                       $this->mwGlobals[$globalKey] = unserialize( serialize( $GLOBALS[$globalKey] ) );
-                               }
-                                       // NOTE; some things such as Closures are not serializable
-                                       // in this case just set the value!
-                               catch ( Exception $e ) {
+                               if ( self::canShallowCopy( $GLOBALS[$globalKey] ) ) {
                                        $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
+                               } elseif (
+                                       // Many MediaWiki types are safe to clone. These are the
+                                       // ones that are most commonly stashed.
+                                       $GLOBALS[$globalKey] instanceof Language ||
+                                       $GLOBALS[$globalKey] instanceof User ||
+                                       $GLOBALS[$globalKey] instanceof FauxRequest
+                               ) {
+                                       $this->mwGlobals[$globalKey] = clone $GLOBALS[$globalKey];
+                               } else {
+                                       try {
+                                               $this->mwGlobals[$globalKey] = unserialize( serialize( $GLOBALS[$globalKey] ) );
+                                       } catch ( Exception $e ) {
+                                               $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
+                                       }
                                }
                        }
                }
index a01f2f5..4721793 100644 (file)
@@ -46,7 +46,7 @@ class HtmlTest extends MediaWikiTestCase {
                $this->assertEquals(
                        '<img/>',
                        Html::element( 'img', null, '' ),
-                       'No close tag for short-tag elements'
+                       'Self-closing tag for short-tag elements'
                );
 
                $this->assertEquals(
@@ -60,12 +60,6 @@ class HtmlTest extends MediaWikiTestCase {
                        Html::element( 'element', [], '' ),
                        'Close tag for empty element (array, string)'
                );
-
-               $this->assertEquals(
-                       '<img/>',
-                       Html::element( 'img', null, '' ),
-                       'Self-closing tag for short-tag elements'
-               );
        }
 
        public function dataXmlMimeType() {
@@ -140,12 +134,6 @@ class HtmlTest extends MediaWikiTestCase {
                        Html::expandAttributes( [ 'selected' ] ),
                        'Boolean attributes have no value when value is true (passed as numerical array)'
                );
-
-               $this->assertEquals(
-                       ' selected=""',
-                       Html::expandAttributes( [ 'selected' => true ] ),
-                       'Boolean attributes have empty string value when value is true'
-               );
        }
 
        /**
index a45c3ae..0e646ea 100644 (file)
@@ -2,6 +2,8 @@
 use Liuggio\StatsdClient\Factory\StatsdDataFactory;
 use MediaWiki\Interwiki\InterwikiLookup;
 use MediaWiki\MediaWikiServices;
+use MediaWiki\Services\DestructibleService;
+use MediaWiki\Services\SalvageableService;
 use MediaWiki\Services\ServiceDisabledException;
 
 /**
@@ -9,7 +11,7 @@ use MediaWiki\Services\ServiceDisabledException;
  *
  * @group MediaWiki
  */
-class MediaWikiServicesTest extends PHPUnit_Framework_TestCase {
+class MediaWikiServicesTest extends MediaWikiTestCase {
 
        /**
         * @return Config
@@ -66,9 +68,69 @@ class MediaWikiServicesTest extends PHPUnit_Framework_TestCase {
                $newServices = $this->newMediaWikiServices();
                $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
 
+               $service1 = $this->getMock( SalvageableService::class );
+               $service1->expects( $this->never() )
+                       ->method( 'salvage' );
+
+               $newServices->defineService(
+                       'Test',
+                       function() use ( $service1 ) {
+                               return $service1;
+                       }
+               );
+
+               // force instantiation
+               $newServices->getService( 'Test' );
+
                MediaWikiServices::resetGlobalInstance( $this->newTestConfig() );
                $theServices = MediaWikiServices::getInstance();
 
+               $this->assertSame(
+                       $service1,
+                       $theServices->getService( 'Test' ),
+                       'service definition should survive reset'
+               );
+
+               $this->assertNotSame( $theServices, $newServices );
+               $this->assertNotSame( $theServices, $oldServices );
+
+               MediaWikiServices::forceGlobalInstance( $oldServices );
+       }
+
+       public function testResetGlobalInstance_quick() {
+               $newServices = $this->newMediaWikiServices();
+               $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
+
+               $service1 = $this->getMock( SalvageableService::class );
+               $service1->expects( $this->never() )
+                       ->method( 'salvage' );
+
+               $service2 = $this->getMock( SalvageableService::class );
+               $service2->expects( $this->once() )
+                       ->method( 'salvage' )
+                       ->with( $service1 );
+
+               // sequence of values the instantiator will return
+               $instantiatorReturnValues = [
+                       $service1,
+                       $service2,
+               ];
+
+               $newServices->defineService(
+                       'Test',
+                       function() use ( &$instantiatorReturnValues ) {
+                               return array_shift( $instantiatorReturnValues );
+                       }
+               );
+
+               // force instantiation
+               $newServices->getService( 'Test' );
+
+               MediaWikiServices::resetGlobalInstance( $this->newTestConfig(), 'quick' );
+               $theServices = MediaWikiServices::getInstance();
+
+               $this->assertSame( $service2, $theServices->getService( 'Test' ) );
+
                $this->assertNotSame( $theServices, $newServices );
                $this->assertNotSame( $theServices, $oldServices );
 
@@ -110,35 +172,42 @@ class MediaWikiServicesTest extends PHPUnit_Framework_TestCase {
                }
 
                MediaWikiServices::forceGlobalInstance( $oldServices );
+               $newServices->destroy();
        }
 
        public function testResetChildProcessServices() {
                $newServices = $this->newMediaWikiServices();
                $oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
 
-               $lbFactory = $this->getMockBuilder( 'LBFactorySimple' )
-                       ->disableOriginalConstructor()
-                       ->getMock();
+               $service1 = $this->getMock( DestructibleService::class );
+               $service1->expects( $this->once() )
+                       ->method( 'destroy' );
 
-               $lbFactory->expects( $this->once() )
+               $service2 = $this->getMock( DestructibleService::class );
+               $service2->expects( $this->never() )
                        ->method( 'destroy' );
 
-               $newServices->redefineService(
-                       'DBLoadBalancerFactory',
-                       function() use ( $lbFactory ) {
-                               return $lbFactory;
+               // sequence of values the instantiator will return
+               $instantiatorReturnValues = [
+                       $service1,
+                       $service2,
+               ];
+
+               $newServices->defineService(
+                       'Test',
+                       function() use ( &$instantiatorReturnValues ) {
+                               return array_shift( $instantiatorReturnValues );
                        }
                );
 
                // force the service to become active, so we can check that it does get destroyed
-               $oldLBFactory = $newServices->getService( 'DBLoadBalancerFactory' );
+               $oldTestService = $newServices->getService( 'Test' );
 
                MediaWikiServices::resetChildProcessServices();
                $finalServices = MediaWikiServices::getInstance();
 
-               $newLBFactory = $finalServices->getService( 'DBLoadBalancerFactory' );
-
-               $this->assertNotSame( $oldLBFactory, $newLBFactory );
+               $newTestService = $finalServices->getService( 'Test' );
+               $this->assertNotSame( $oldTestService, $newTestService );
 
                MediaWikiServices::forceGlobalInstance( $oldServices );
        }
index 933777c..f22e123 100644 (file)
@@ -166,6 +166,55 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
                $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
        }
 
+       public function testImportWiring() {
+               $services = $this->newServiceContainer();
+
+               $wiring = [
+                       'Foo' => function() {
+                               return 'Foo!';
+                       },
+                       'Bar' => function() {
+                               return 'Bar!';
+                       },
+                       'Car' => function() {
+                               return 'FUBAR!';
+                       },
+               ];
+
+               $services->applyWiring( $wiring );
+
+               $newServices = $this->newServiceContainer();
+
+               // define a service before importing, so we can later check that
+               // existing service instances survive importWiring()
+               $newServices->defineService( 'Car', function() {
+                       return 'Car!';
+               } );
+
+               // force instantiation
+               $newServices->getService( 'Car' );
+
+               // Define another service, so we can later check that extra wiring
+               // is not lost.
+               $newServices->defineService( 'Xar', function() {
+                       return 'Xar!';
+               } );
+
+               // import wiring, but skip `Bar`
+               $newServices->importWiring( $services, [ 'Bar' ] );
+
+               $this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' );
+               $this->assertSame( 'Foo!', $newServices->getService( 'Foo' ) );
+
+               // import all wiring, but preserve existing service instance
+               $newServices->importWiring( $services );
+
+               $this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' );
+               $this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) );
+               $this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' );
+               $this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' );
+       }
+
        public function testLoadWiringFiles() {
                $services = $this->newServiceContainer();
 
@@ -220,6 +269,27 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
                $this->assertSame( $theService1, $services->getService( $name ) );
        }
 
+       public function testRedefineService_disabled() {
+               $services = $this->newServiceContainer( [ 'Foo' ] );
+
+               $theService1 = new stdClass();
+               $name = 'TestService92834576';
+
+               $services->defineService( $name, function() {
+                       return 'Foo';
+               } );
+
+               // disable the service. we should be able to redefine it anyway.
+               $services->disableService( $name );
+
+               $services->redefineService( $name, function() use ( $theService1 ) {
+                       return $theService1;
+               } );
+
+               // force instantiation, check result
+               $this->assertSame( $theService1, $services->getService( $name ) );
+       }
+
        public function testRedefineService_fail_undefined() {
                $services = $this->newServiceContainer();
 
@@ -294,13 +364,6 @@ class ServiceContainerTest extends PHPUnit_Framework_TestCase {
                $this->assertContains( 'Bar', $services->getServiceNames() );
                $this->assertContains( 'Qux', $services->getServiceNames() );
 
-               // re-enable Bar service
-               $services->redefineService( 'Bar', function() {
-                       return new stdClass();
-               } );
-
-               $services->getService( 'Bar' );
-
                $this->setExpectedException( 'MediaWiki\Services\ServiceDisabledException' );
                $services->getService( 'Qux' );
        }
index 13bfa46..247b6e4 100644 (file)
@@ -129,19 +129,28 @@ class TestUser {
                        throw new MWException( "Passed User has not been added to the database yet!" );
                }
 
-               if ( $user->checkPassword( $password ) === true ) {
-                       return;  // Nothing to do.
-               }
-
-               $passwordFactory = new PasswordFactory();
-               $passwordFactory->init( RequestContext::getMain()->getConfig() );
-               $passwordHash = $passwordFactory->newFromPlaintext( $password );
-               wfGetDB( DB_MASTER )->update(
+               $dbw = wfGetDB( DB_MASTER );
+               $row = $dbw->selectRow(
                        'user',
-                       [ 'user_password' => $passwordHash->toString() ],
+                       [ 'user_password' ],
                        [ 'user_id' => $user->getId() ],
                        __METHOD__
                );
+               if ( !$row ) {
+                       throw new MWException( "Passed User has an ID but is not in the database?" );
+               }
+
+               $passwordFactory = new PasswordFactory();
+               $passwordFactory->init( RequestContext::getMain()->getConfig() );
+               if ( !$passwordFactory->newFromCiphertext( $row->user_password )->equals( $password ) ) {
+                       $passwordHash = $passwordFactory->newFromPlaintext( $password );
+                       $dbw->update(
+                               'user',
+                               [ 'user_password' => $passwordHash->toString() ],
+                               [ 'user_id' => $user->getId() ],
+                               __METHOD__
+                       );
+               }
        }
 
        /**
index 75c5b50..4a30292 100644 (file)
@@ -56,6 +56,8 @@ class ActionTest extends MediaWikiTestCase {
                        // Null and non-existing values
                        [ 'null', null ],
                        [ 'undeclared', null ],
+                       [ '', null ],
+                       [ false, null ],
                ];
        }
 
@@ -135,39 +137,22 @@ class ActionTest extends MediaWikiTestCase {
                $this->assertType( $expected ?: 'null', $action );
        }
 
-       public function emptyActionProvider() {
-               return [
-                       [ null ],
-                       [ false ],
-                       [ '' ],
-               ];
-       }
-
-       /**
-        * @dataProvider emptyActionProvider
-        */
-       public function testEmptyAction_doesNotExist( $requestedAction ) {
-               $exists = Action::exists( $requestedAction );
+       public function testNull_doesNotExist() {
+               $exists = Action::exists( null );
 
                $this->assertFalse( $exists );
        }
 
-       /**
-        * @dataProvider emptyActionProvider
-        */
-       public function testEmptyAction_defaultsToView( $requestedAction ) {
-               $context = $this->getContext( $requestedAction );
+       public function testNull_defaultsToView() {
+               $context = $this->getContext( null );
                $actionName = Action::getActionName( $context );
 
                $this->assertEquals( 'view', $actionName );
        }
 
-       /**
-        * @dataProvider emptyActionProvider
-        */
-       public function testEmptyAction_canNotBeInstantiated( $requestedAction ) {
+       public function testNull_canNotBeInstantiated() {
                $page = $this->getPage();
-               $action = Action::factory( $requestedAction, $page );
+               $action = Action::factory( null, $page );
 
                $this->assertNull( $action );
        }
diff --git a/tests/phpunit/includes/api/ApiSetNotificationTimestampIntegrationTest.php b/tests/phpunit/includes/api/ApiSetNotificationTimestampIntegrationTest.php
new file mode 100644 (file)
index 0000000..ef4f513
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+use MediaWiki\MediaWikiServices;
+
+/**
+ * @author Addshore
+ * @covers ApiSetNotificationTimestamp
+ * @group API
+ * @group medium
+ * @group Database
+ */
+class ApiSetNotificationTimestampIntegrationTest extends ApiTestCase {
+
+       protected function setUp() {
+               parent::setUp();
+               self::$users[__CLASS__] = new TestUser( __CLASS__ );
+               $this->doLogin( __CLASS__ );
+       }
+
+       public function testStuff() {
+               $user = self::$users[__CLASS__]->getUser();
+               $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+
+               $user->addWatch( $page->getTitle() );
+
+               $result = $this->doApiRequestWithToken(
+                       [
+                               'action' => 'setnotificationtimestamp',
+                               'timestamp' => '20160101020202',
+                               'pageids' => $page->getId(),
+                       ],
+                       null,
+                       $user
+               );
+
+               $this->assertEquals(
+                       [
+                               'batchcomplete' => true,
+                               'setnotificationtimestamp' => [
+                                       [ 'ns' => 0, 'title' => 'UTPage', 'notificationtimestamp' => '2016-01-01T02:02:02Z' ]
+                               ],
+                       ],
+                       $result[0]
+               );
+
+               $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
+               $this->assertEquals(
+                       $watchedItemStore->getNotificationTimestampsBatch( $user, [ $page->getTitle() ] ),
+                       [ [ 'UTPage' => '20160101020202' ] ]
+               );
+       }
+
+}
index 2c1d1e6..f42cb95 100644 (file)
@@ -44,6 +44,42 @@ class ConfigFactoryTest extends MediaWikiTestCase {
                $this->assertNotSame( $config1, $config2 );
        }
 
+       /**
+        * @covers ConfigFactory::register
+        */
+       public function testSalvage() {
+               $oldFactory = new ConfigFactory();
+               $oldFactory->register( 'foo', 'GlobalVarConfig::newInstance' );
+               $oldFactory->register( 'bar', 'GlobalVarConfig::newInstance' );
+               $oldFactory->register( 'quux', 'GlobalVarConfig::newInstance' );
+
+               // instantiate two of the three defined configurations
+               $foo = $oldFactory->makeConfig( 'foo' );
+               $bar = $oldFactory->makeConfig( 'bar' );
+               $quux = $oldFactory->makeConfig( 'quux' );
+
+               // define new config instance
+               $newFactory = new ConfigFactory();
+               $newFactory->register( 'foo', 'GlobalVarConfig::newInstance' );
+               $newFactory->register( 'bar', function() {
+                       return new HashConfig();
+               } );
+
+               // "foo" and "quux" are defined in the old and the new factory.
+               // The old factory has instances for "foo" and "bar", but not "quux".
+               $newFactory->salvage( $oldFactory );
+
+               $newFoo = $newFactory->makeConfig( 'foo' );
+               $this->assertSame( $foo, $newFoo, 'existing instance should be salvaged' );
+
+               $newBar = $newFactory->makeConfig( 'bar' );
+               $this->assertNotSame( $bar, $newBar, 'don\'t salvage if callbacks differ' );
+
+               // the new factory doesn't have quux defined, so the quux instance should not be salvaged
+               $this->setExpectedException( 'ConfigException' );
+               $newFactory->makeConfig( 'quux' );
+       }
+
        /**
         * @covers ConfigFactory::register
         */
index c1449ea..5c65483 100644 (file)
@@ -9,11 +9,13 @@ class MWDebugTest extends MediaWikiTestCase {
        }
 
        public static function setUpBeforeClass() {
+               parent::setUpBeforeClass();
                MWDebug::init();
                MediaWiki\suppressWarnings();
        }
 
        public static function tearDownAfterClass() {
+               parent::tearDownAfterClass();
                MWDebug::deinit();
                MediaWiki\restoreWarnings();
        }