Merge "Validate BlockID cookie before use"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 13 Feb 2017 23:48:22 +0000 (23:48 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 13 Feb 2017 23:48:22 +0000 (23:48 +0000)
103 files changed:
.eslintrc.json
RELEASE-NOTES-1.29
autoload.php
composer.json
includes/AjaxDispatcher.php
includes/FileDeleteForm.php
includes/OutputPage.php
includes/WatchedItemStore.php
includes/actions/InfoAction.php
includes/api/i18n/ar.json
includes/api/i18n/es.json
includes/api/i18n/fr.json
includes/api/i18n/gl.json
includes/api/i18n/ja.json
includes/api/i18n/ko.json
includes/api/i18n/pl.json
includes/api/i18n/ru.json
includes/api/i18n/sv.json
includes/db/DatabaseMssql.php [deleted file]
includes/db/DatabaseOracle.php
includes/db/MWLBFactory.php
includes/filebackend/filejournal/DBFileJournal.php
includes/filerepo/file/ForeignDBFile.php
includes/installer/MysqlUpdater.php
includes/installer/i18n/bg.json
includes/installer/i18n/ko.json
includes/jobqueue/jobs/HTMLCacheUpdateJob.php
includes/libs/rdbms/ChronologyProtector.php
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMssql.php [new file with mode: 0644]
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/DatabasePostgres.php
includes/libs/rdbms/database/DatabaseSqlite.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/database/position/DBMasterPos.php
includes/libs/rdbms/database/position/MySQLMasterPos.php
includes/libs/rdbms/database/resultwrapper/IResultWrapper.php [new file with mode: 0644]
includes/libs/rdbms/database/resultwrapper/ResultWrapper.php
includes/libs/rdbms/encasing/Blob.php
includes/libs/rdbms/encasing/IBlob.php [new file with mode: 0644]
includes/libs/rdbms/encasing/MssqlBlob.php
includes/libs/rdbms/encasing/PostgresBlob.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/revisiondelete/RevDelList.php
includes/specials/SpecialExport.php
includes/utils/AutoloadGenerator.php
includes/widget/search/InterwikiSearchResultWidget.php
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/be-tarask.json
languages/i18n/bqi.json
languages/i18n/bs.json
languages/i18n/ca.json
languages/i18n/ckb.json
languages/i18n/cs.json
languages/i18n/csb.json
languages/i18n/diq.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/eu.json
languages/i18n/fi.json
languages/i18n/hr.json
languages/i18n/ia.json
languages/i18n/io.json
languages/i18n/ko.json
languages/i18n/ku-latn.json
languages/i18n/lv.json
languages/i18n/mg.json
languages/i18n/ms.json
languages/i18n/nb.json
languages/i18n/nn.json
languages/i18n/oc.json
languages/i18n/pl.json
languages/i18n/shn.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/te.json
languages/i18n/tt-cyrl.json
languages/i18n/zh-hant.json
resources/src/mediawiki.legacy/shared.css
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js
resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
resources/src/mediawiki.rcfilters/mw.rcfilters.init.js
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.CapsuleItemWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterCapsuleMultiselectWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterItemWidget.less
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.CapsuleItemWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterCapsuleMultiselectWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemWidget.js
resources/src/mediawiki.special/mediawiki.special.apisandbox.js
resources/src/mediawiki/mediawiki.js
resources/src/mediawiki/mediawiki.storage.js
resources/src/startup.js
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/db/DatabaseMysqlBaseTest.php
tests/phpunit/includes/db/DatabaseSqliteTest.php
tests/phpunit/includes/db/LBFactoryTest.php
tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.test.js

index 044dd72..98d0f10 100644 (file)
@@ -9,7 +9,6 @@
                "require": false,
                "module": false,
                "mediaWiki": false,
-               "mwPerformance": false,
                "OO": false
        },
        "rules": {
index ebd967e..3ba6577 100644 (file)
@@ -69,6 +69,7 @@ production.
   to the page content language, not wiki content language.
 * (T27187) Search suggestions based on jquery.suggestions will now correctly only
   highlight prefix matches in the results.
+* (T157035) "new mw.Uri()" was ignoring options when using default URI.
 
 === Action API changes in 1.29 ===
 * Submitting sensitive authentication request parameters to action=clientlogin,
index 1cb8a14..0e719ae 100644 (file)
@@ -313,7 +313,6 @@ $wgAutoloadLocalClasses = [
        'DBExpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBExpectedError.php',
        'DBFileJournal' => __DIR__ . '/includes/filebackend/filejournal/DBFileJournal.php',
        'DBLockManager' => __DIR__ . '/includes/libs/lockmanager/DBLockManager.php',
-       'DBMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/DBMasterPos.php',
        'DBQueryError' => __DIR__ . '/includes/libs/rdbms/exception/DBQueryError.php',
        'DBReadOnlyError' => __DIR__ . '/includes/libs/rdbms/exception/DBReadOnlyError.php',
        'DBReplicationWaitError' => __DIR__ . '/includes/libs/rdbms/exception/DBReplicationWaitError.php',
@@ -327,7 +326,7 @@ $wgAutoloadLocalClasses = [
        'DatabaseInstaller' => __DIR__ . '/includes/installer/DatabaseInstaller.php',
        'DatabaseLag' => __DIR__ . '/maintenance/lag.php',
        'DatabaseLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
-       'DatabaseMssql' => __DIR__ . '/includes/db/DatabaseMssql.php',
+       'DatabaseMssql' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMssql.php',
        'DatabaseMysql' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysql.php',
        'DatabaseMysqlBase' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqlBase.php',
        'DatabaseMysqli' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqli.php',
@@ -970,7 +969,6 @@ $wgAutoloadLocalClasses = [
        'MoveLogFormatter' => __DIR__ . '/includes/logging/MoveLogFormatter.php',
        'MovePage' => __DIR__ . '/includes/MovePage.php',
        'MovePageForm' => __DIR__ . '/includes/specials/SpecialMovepage.php',
-       'MssqlBlob' => __DIR__ . '/includes/libs/rdbms/encasing/MssqlBlob.php',
        'MssqlField' => __DIR__ . '/includes/libs/rdbms/field/MssqlField.php',
        'MssqlInstaller' => __DIR__ . '/includes/installer/MssqlInstaller.php',
        'MssqlResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php',
@@ -982,7 +980,6 @@ $wgAutoloadLocalClasses = [
        'MutableContext' => __DIR__ . '/includes/context/MutableContext.php',
        'MwSql' => __DIR__ . '/maintenance/sql.php',
        'MySQLField' => __DIR__ . '/includes/libs/rdbms/field/MySQLField.php',
-       'MySQLMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/MySQLMasterPos.php',
        'MySqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/MySqlLockManager.php',
        'MysqlInstaller' => __DIR__ . '/includes/installer/MysqlInstaller.php',
        'MysqlUpdater' => __DIR__ . '/includes/installer/MysqlUpdater.php',
@@ -1095,7 +1092,6 @@ $wgAutoloadLocalClasses = [
        'PopulateRevisionLength' => __DIR__ . '/maintenance/populateRevisionLength.php',
        'PopulateRevisionSha1' => __DIR__ . '/maintenance/populateRevisionSha1.php',
        'PostgreSqlLockManager' => __DIR__ . '/includes/libs/lockmanager/PostgreSqlLockManager.php',
-       'PostgresBlob' => __DIR__ . '/includes/libs/rdbms/encasing/PostgresBlob.php',
        'PostgresField' => __DIR__ . '/includes/libs/rdbms/field/PostgresField.php',
        'PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php',
        'PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php',
@@ -1582,12 +1578,16 @@ $wgAutoloadLocalClasses = [
        'WikiRevision' => __DIR__ . '/includes/import/WikiRevision.php',
        'WikiStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
        'WikiTextStructure' => __DIR__ . '/includes/content/WikiTextStructure.php',
+       'Wikimedia\\Rdbms\\Blob' => __DIR__ . '/includes/libs/rdbms/encasing/Blob.php',
        'Wikimedia\\Rdbms\\ChronologyProtector' => __DIR__ . '/includes/libs/rdbms/ChronologyProtector.php',
        'Wikimedia\\Rdbms\\ConnectionManager' => __DIR__ . '/includes/libs/rdbms/connectionmanager/ConnectionManager.php',
+       'Wikimedia\\Rdbms\\DBMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/DBMasterPos.php',
        'Wikimedia\\Rdbms\\DatabaseDomain' => __DIR__ . '/includes/libs/rdbms/database/DatabaseDomain.php',
+       'Wikimedia\\Rdbms\\IBlob' => __DIR__ . '/includes/libs/rdbms/encasing/IBlob.php',
        'Wikimedia\\Rdbms\\ILBFactory' => __DIR__ . '/includes/libs/rdbms/lbfactory/ILBFactory.php',
        'Wikimedia\\Rdbms\\ILoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/ILoadBalancer.php',
        'Wikimedia\\Rdbms\\ILoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/ILoadMonitor.php',
+       'Wikimedia\\Rdbms\\IResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/IResultWrapper.php',
        'Wikimedia\\Rdbms\\LBFactory' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactory.php',
        'Wikimedia\\Rdbms\\LBFactoryMulti' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactoryMulti.php',
        'Wikimedia\\Rdbms\\LBFactorySimple' => __DIR__ . '/includes/libs/rdbms/lbfactory/LBFactorySimple.php',
@@ -1596,6 +1596,9 @@ $wgAutoloadLocalClasses = [
        'Wikimedia\\Rdbms\\LoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitor.php',
        'Wikimedia\\Rdbms\\LoadMonitorMySQL' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php',
        'Wikimedia\\Rdbms\\LoadMonitorNull' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php',
+       'Wikimedia\\Rdbms\\MssqlBlob' => __DIR__ . '/includes/libs/rdbms/encasing/MssqlBlob.php',
+       'Wikimedia\\Rdbms\\MySQLMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/MySQLMasterPos.php',
+       'Wikimedia\\Rdbms\\PostgresBlob' => __DIR__ . '/includes/libs/rdbms/encasing/PostgresBlob.php',
        'Wikimedia\\Rdbms\\SessionConsistentConnectionManager' => __DIR__ . '/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManager.php',
        'Wikimedia\\Rdbms\\TransactionProfiler' => __DIR__ . '/includes/libs/rdbms/TransactionProfiler.php',
        'WikitextContent' => __DIR__ . '/includes/content/WikitextContent.php',
index 71338a4..d41492e 100644 (file)
@@ -55,7 +55,9 @@
                "nikic/php-parser": "2.1.0",
                "nmred/kafka-php": "0.1.5",
                "phpunit/phpunit": "4.8.31",
-               "wikimedia/avro": "1.7.7"
+               "wikimedia/avro": "1.7.7",
+               "hamcrest/hamcrest-php": "^2.0",
+               "wmde/hamcrest-html-matchers": "^0.1.0"
        },
        "suggest": {
                "ext-apc": "Local data and opcode cache",
                        "ComposerHookHandler": "includes/composer"
                }
        },
+       "autoload-dev": {
+               "files": [
+                       "vendor/hamcrest/hamcrest-php/hamcrest/Hamcrest.php",
+                       "vendor/wmde/hamcrest-html-matchers/src/functions.php"
+               ]
+       },
        "scripts": {
                "lint": "parallel-lint --exclude vendor",
                "phpcs": "phpcs -p -s",
index d444a27..2adbc80 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup Ajax
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * @defgroup Ajax Ajax
  */
@@ -135,7 +137,8 @@ class AjaxDispatcher {
                                        }
 
                                        // Make sure DB commit succeeds before sending a response
-                                       wfGetLBFactory()->commitMasterChanges( __METHOD__ );
+                                       $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                                       $lbFactory->commitMasterChanges( __METHOD__ );
 
                                        $result->sendHeaders();
                                        $result->printText();
index 82af081..f284d92 100644 (file)
@@ -206,7 +206,8 @@ class FileDeleteForm {
                                        $dbw->endAtomic( __METHOD__ );
                                } else {
                                        // Page deleted but file still there? rollback page delete
-                                       wfGetLBFactory()->rollbackMasterChanges( __METHOD__ );
+                                       $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                                       $lbFactory->rollbackMasterChanges( __METHOD__ );
                                }
                        } else {
                                // Done; nothing changed
index 91fc75c..af82436 100644 (file)
@@ -3697,6 +3697,8 @@ class OutputPage extends ContextSource {
         */
        public static function transformResourcePath( Config $config, $path ) {
                global $IP;
+
+               $localDir = $IP;
                $remotePathPrefix = $config->get( 'ResourceBasePath' );
                if ( $remotePathPrefix === '' ) {
                        // The configured base path is required to be empty string for
@@ -3710,8 +3712,18 @@ class OutputPage extends ContextSource {
                        // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
                        return $path;
                }
+               // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
+               // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
+               // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
+               // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
+               $uploadPath = $config->get( 'UploadPath' );
+               if ( strpos( $path, $uploadPath ) === 0 ) {
+                       $localDir = $config->get( 'UploadDirectory' );
+                       $remotePathPrefix = $remotePath = $uploadPath;
+               }
+
                $path = RelPath\getRelativePath( $path, $remotePath );
-               return self::transformFilePath( $remotePathPrefix, $IP, $path );
+               return self::transformFilePath( $remotePathPrefix, $localDir, $path );
        }
 
        /**
index 3cdc59c..858d87b 100644 (file)
@@ -2,6 +2,7 @@
 
 use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
 use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
 use Wikimedia\Assert\Assert;
 use Wikimedia\ScopedCallback;
 
@@ -734,7 +735,7 @@ class WatchedItemStore implements StatsdAwareInterface {
                                        global $wgUpdateRowsPerQuery;
 
                                        $dbw = $this->getConnectionRef( DB_MASTER );
-                                       $factory = wfGetLBFactory();
+                                       $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                                        $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
 
                                        $watchersChunks = array_chunk( $watchers, $wgUpdateRowsPerQuery );
index 5fb83b3..167b709 100644 (file)
@@ -835,7 +835,7 @@ class InfoAction extends FormlessAction {
                $real_names = [];
                $user_names = [];
                $anon_ips = [];
-               $linkRenderer = MediaWikiServices::getLinkRenderer();
+               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
 
                # Sift for real versus user names
                /** @var $user User */
index 86078d4..1222195 100644 (file)
@@ -8,7 +8,8 @@
                        "Hiba Alshawi",
                        "Maroen1990",
                        "محمد أحمد عبد الفتاح",
-                       "ديفيد"
+                       "ديفيد",
+                       "ASHmed"
                ]
        },
        "apihelp-main-param-action": "أي فعل للعمل.",
        "apihelp-protect-example-protect": "حماية صفحة.",
        "apihelp-protect-example-unprotect": "إلغاء حماية الصفحة من خلال وضع قيود ل<kbd>all</kbd> (أي يُسمَح أي شخص باتخاذ الإجراءات).",
        "apihelp-protect-example-unprotect2": "إلغاء حماية الصفحة عن طريق عدم وضع أية قيود.",
+       "apihelp-purge-description": "مسح ذاكرة التخزين المؤقت للعناوين المعطاة",
        "apihelp-purge-param-forcelinkupdate": "تحديث جداول الروابط.",
        "apihelp-purge-param-forcerecursivelinkupdate": "تحديث جدول الروابط، وتحديث جداول الروابط لأية صفحة تستخدم هذه الصفحة كقالب.",
        "apihelp-purge-example-simple": "إفراغ كاش <kbd>Main Page</kbd> وصفحة <kbd>API</kbd>.",
index ae51f12..e7a9d2c 100644 (file)
        "apihelp-removeauthenticationdata-description": "Elimina los datos de autentificación del usuario actual.",
        "apihelp-removeauthenticationdata-example-simple": "Trata de eliminar los datos del usuario actual para <kbd>FooAuthenticationRequest</kbd>.",
        "apihelp-resetpassword-description": "Enviar un email de reinicialización de la contraseña a un usuario.",
+       "apihelp-resetpassword-param-user": "Usuario en proceso de reinicialización",
+       "apihelp-resetpassword-param-email": "Dirección de correo electrónico del usuario que se va a reinicializar",
        "apihelp-resetpassword-example-user": "Enviar un correo de recuperación de contraseña al usuario <kbd>Ejemplo</kbd>.",
        "apihelp-resetpassword-example-email": "Enviar un correo de recuperación de contraseña para todos los usuarios con dirección de correo electrónico <kbd>usuario@ejemplo.com</kbd>.",
        "apihelp-revisiondelete-description": "Eliminar y restaurar revisiones",
        "apihelp-watch-example-unwatch": "Dejar de vigilar la <kbd>Main Page</kbd>.",
        "apihelp-watch-example-generator": "Seguir las primeras páginas del espacio de nombres principal.",
        "apihelp-format-example-generic": "Devolver el resultado de la consulta en formato $1.",
+       "apihelp-format-param-wrappedhtml": "Devolver el HTML con resaltado sintáctico y los módulos ResourceLoader asociados en forma de objeto JSON.",
        "apihelp-json-description": "Extraer los datos de salida en formato JSON.",
        "apihelp-json-param-callback": "Si se especifica, envuelve la salida dentro de una llamada a una función dada. Por motivos de seguridad, cualquier dato específico del usuario estará restringido.",
        "apihelp-json-param-utf8": "Si se especifica, codifica la mayoría (pero no todos) de los caracteres no pertenecientes a ASCII como UTF-8 en lugar de reemplazarlos por secuencias de escape hexadecimal. Toma el comportamiento por defecto si <var>formatversion</var> no es <kbd>1</kbd>.",
        "apihelp-json-param-ascii": "Si se especifica, codifica todos los caracteres no pertenecientes a ASCII mediante secuencias de escape hexadecimal. Toma el comportamiento por defecto si <var>formatversion</var> no es <kbd>1</kbd>.",
        "apihelp-json-param-formatversion": "Formato de salida:\n;1: Formato retrocompatible (booleanos con estilo XML, claves <samp>*</samp> para nodos de contenido, etc.).\n;2: Formato moderno experimental. ¡Atención, las especificaciones pueden cambiar!\n;latest: Utiliza el último formato (actualmente <kbd>2</kbd>). Puede cambiar sin aviso.",
+       "apihelp-jsonfm-description": "Producir los datos de salida en formato JSON (con resaltado sintáctico en HTML).",
        "apihelp-none-description": "No extraer nada.",
        "apihelp-php-description": "Extraer los datos de salida en formato serializado PHP.",
+       "apihelp-php-param-formatversion": "Formato de salida:\n;1: Formato retrocompatible (booleanos con estilo XML, claves <samp>*</samp> para nodos de contenido, etc.).\n;2: Formato moderno experimental. ¡Atención, las especificaciones pueden cambiar!\n;latest: Utilizar el último formato (actualmente <kbd>2</kbd>). Puede cambiar sin aviso.",
+       "apihelp-phpfm-description": "Producir los datos de salida en formato PHP serializado (con resaltado sintáctico en HTML).",
        "apihelp-rawfm-description": "Extraer los datos de salida, incluidos los elementos de depuración, en formato JSON (embellecido en HTML).",
+       "apihelp-xml-description": "Producir los datos de salida en formato XML.",
        "apihelp-xml-param-xslt": "Si se especifica, añade la página nombrada como una hoja de estilo XSL. El valor debe ser un título en el espacio de nombres {{ns:MediaWiki}} que termine en <code>.xsl</code>.",
        "apihelp-xml-param-includexmlnamespace": "Si se especifica, añade un espacio de nombres XML.",
+       "apihelp-xmlfm-description": "Producir los datos de salida en formato XML (con resaltado sintáctico en HTML).",
        "api-format-title": "Resultado de la API de MediaWiki",
        "api-format-prettyprint-header": "Esta es la representación en HTML del formato $1. HTML es adecuado para realizar tareas de depuración, pero no para utilizarlo en aplicaciones.\n\nUtiliza el parámetro <var>format</var> para modificar el formato de salida. Para ver la representación no HTML del formato $1, emplea <kbd>format=$2</kbd>.\n\nPara obtener más información, consulta la [[mw:API|documentación completa]] o la [[Special:ApiHelp/main|ayuda de API]].",
        "api-format-prettyprint-status": "Esta respuesta se devolvería con el estado HTTP $1 $2.",
index b2d1100..5a0dd30 100644 (file)
        "apihelp-help-param-recursivesubmodules": "Inclure l’aide pour les sous-modules de façon récursive.",
        "apihelp-help-param-helpformat": "Format de sortie de l’aide.",
        "apihelp-help-param-wrap": "Inclut la sortie dans une structure de réponse API standard.",
-       "apihelp-help-param-toc": "Inclure une table des matières dans la sortir HTML.",
+       "apihelp-help-param-toc": "Inclure une table des matières dans la sortie HTML.",
        "apihelp-help-example-main": "Aide pour le module principal",
        "apihelp-help-example-submodules": "Aide pour <kbd>action=query</kbd> et tous ses sous-modules.",
-       "apihelp-help-example-recursive": "Toute l’aide sur une page",
-       "apihelp-help-example-help": "Aide pour le module d’aide lui-même",
-       "apihelp-help-example-query": "Aide pour deux sous-modules de recherche",
+       "apihelp-help-example-recursive": "Toute l’aide sur une page.",
+       "apihelp-help-example-help": "Aide pour le module d’aide lui-même.",
+       "apihelp-help-example-query": "Aide pour deux sous-modules de recherche.",
        "apihelp-imagerotate-description": "Faire pivoter une ou plusieurs images.",
        "apihelp-imagerotate-param-rotation": "Degrés de rotation de l’image dans le sens des aiguilles d’une montre.",
        "apihelp-imagerotate-param-tags": "Balises à appliquer à l’entrée dans le journal de téléchargement.",
        "apihelp-move-description": "Déplacer une page.",
        "apihelp-move-param-from": "Titre de la page à renommer. Impossible de l’utiliser avec <var>$1fromid</var>.",
        "apihelp-move-param-fromid": "ID de la page à renommer. Impossible à utiliser avec <var>$1from</var>.",
-       "apihelp-move-param-to": "Titre de la page renommée.",
+       "apihelp-move-param-to": "Nouveau titre de la page.",
        "apihelp-move-param-reason": "Motif du renommage.",
        "apihelp-move-param-movetalk": "Renommer la page de discussion, si elle existe.",
        "apihelp-move-param-movesubpages": "Renommer les sous-pages, le cas échéant.",
        "apihelp-move-param-noredirect": "Ne pas créer une redirection.",
-       "apihelp-move-param-watch": "Ajouter une page et la redirection à liste de suivi de l'utilisateur actuel.",
+       "apihelp-move-param-watch": "Ajouter la page et la redirection, à la liste de suivi de l'utilisateur actuel.",
        "apihelp-move-param-unwatch": "Supprimer la page et la redirection de la liste de suivi de l'utilisateur actuel.",
        "apihelp-move-param-watchlist": "Ajouter ou supprimer sans condition la page de la liste de suivi de l'utilisateur actuel, utiliser les préférences ou ne pas changer le suivi.",
        "apihelp-move-param-ignorewarnings": "Ignorer tous les avertissements.",
        "apihelp-move-param-tags": "Modifier les balises à appliquer à l'entrée du journal des renommages et à la version zéro de la page de destination.",
-       "apihelp-move-example-move": "Déplacer <kbd>Badtitle</kbd> en <kbd>Goodtitle</kbd> sans garder de redirection.",
+       "apihelp-move-example-move": "Renommer <kbd>Badtitle</kbd> en <kbd>Goodtitle</kbd> sans garder de redirection.",
        "apihelp-opensearch-description": "Rechercher dans le wiki en utilisant le protocole OpenSearch.",
        "apihelp-opensearch-param-search": "Chaîne de caractères cherchée.",
        "apihelp-opensearch-param-limit": "Nombre maximal de résultats à renvoyer.",
        "apihelp-protect-param-expiry": "Horodatages d’expiration. Si un seul horodatage est fourni, il sera utilisé pour toutes les protections. Utiliser <kbd>infinite</kbd>, <kbd>indefinite</kbd>, <kbd>infinity</kbd> ou <kbd>never</kbd> pour une protection sans expiration.",
        "apihelp-protect-param-reason": "Motif de (dé)protection.",
        "apihelp-protect-param-tags": "Modifier les balises à appliquer à l’entrée dans le journal de protection.",
-       "apihelp-protect-param-cascade": "Activer la protection en cascade (c’est-à-dire protéger les modèles transclus et les images utilisées dans cette page). Ignoré si aucun des niveaux de protection fournis ne prend en charge la mise en cascade.",
+       "apihelp-protect-param-cascade": "Activer la protection en cascade (c’est-à-dire protéger les modèles transclus et les images utilisés dans cette page). Ignoré si aucun des niveaux de protection fournis ne prend en charge la mise en cascade.",
        "apihelp-protect-param-watch": "Si activé, ajouter la page (dé)protégée à la liste de suivi de l'utilisateur actuel.",
        "apihelp-protect-param-watchlist": "Ajouter ou supprimer sans condition la page de la liste de suivi de l'utilisateur actuel, utiliser les préférences ou ne pas modifier le suivi.",
        "apihelp-protect-example-protect": "Protéger une page",
        "apihelp-purge-example-simple": "Purger les pages <kbd>Main Page</kbd> et <kbd>API</kbd>.",
        "apihelp-purge-example-generator": "Purger les 10 premières pages de l’espace de noms principal",
        "apihelp-query-description": "Extraire des données de et sur MediaWiki.\n\nToutes les modifications de données devront d’abord utiliser une requête pour obtenir un jeton, afin d’éviter les abus de la part de sites malveillants.",
-       "apihelp-query-param-prop": "Quelles propriétés obtenir des pages demandées.",
+       "apihelp-query-param-prop": "Quelles propriétés obtenir pour les pages demandées.",
        "apihelp-query-param-list": "Quelles listes obtenir.",
        "apihelp-query-param-meta": "Quelles métadonnées obtenir.",
        "apihelp-query-param-indexpageids": "Inclure une section pageids supplémentaire listant tous les IDs de page renvoyés.",
        "apihelp-query+allcategories-param-from": "La catégorie depuis laquelle démarrer l’énumération.",
        "apihelp-query+allcategories-param-to": "La catégorie à laquelle terminer l’énumération.",
        "apihelp-query+allcategories-param-prefix": "Rechercher tous les titres de catégorie qui commencent avec cette valeur.",
-       "apihelp-query+allcategories-param-dir": "Direction dans laquelle trier.",
+       "apihelp-query+allcategories-param-dir": "Ordre dans lequel trier.",
        "apihelp-query+allcategories-param-min": "Renvoyer uniquement les catégories avec au moins ce nombre de membres.",
        "apihelp-query+allcategories-param-max": "Renvoyer uniquement les catégories avec au plus ce nombre de membres.",
        "apihelp-query+allcategories-param-limit": "Combien de catégories renvoyer.",
        "apihelp-query+allfileusages-example-generator": "Obtient les pages contenant les fichiers",
        "apihelp-query+allimages-description": "Énumérer toutes les images séquentiellement.",
        "apihelp-query+allimages-param-sort": "Propriété par laquelle trier.",
-       "apihelp-query+allimages-param-dir": "La direction dans laquelle lister.",
+       "apihelp-query+allimages-param-dir": "L'ordre dans laquel lister.",
        "apihelp-query+allimages-param-from": "Le titre de l’image depuis laquelle démarrer l’énumération. Ne peut être utilisé qu’avec $1sort=name.",
        "apihelp-query+allimages-param-to": "Le titre de l’image auquel arrêter l’énumération. Ne peut être utilisé qu’avec $1sort=name.",
        "apihelp-query+allimages-param-start": "L’horodatage depuis lequel énumérer. Ne peut être utilisé qu’avec $1sort=timestamp.",
-       "apihelp-query+allimages-param-end": "L’horodatage de fin de l’énumération. Ne peut être utilisé qu’avec $1sort=timestamp.",
+       "apihelp-query+allimages-param-end": "L’horodatage de la fin d’énumération. Ne peut être utilisé qu’avec $1sort=timestamp.",
        "apihelp-query+allimages-param-prefix": "Rechercher toutes les images dont le titre commence par cette valeur. Utilisable uniquement avec $1sort=name.",
        "apihelp-query+allimages-param-minsize": "Restreindre aux images avec au moins ce nombre d’octets.",
        "apihelp-query+allimages-param-maxsize": "Restreindre aux images avec au plus ce nombre d’octets.",
        "apihelp-query+allimages-param-mime": "Quels types MIME rechercher, par ex. <kbd>image/jpeg</kbd>.",
        "apihelp-query+allimages-param-limit": "Combien d’images renvoyer au total.",
        "apihelp-query+allimages-example-B": "Afficher une liste des fichiers commençant par la lettre <kbd>B</kbd>.",
-       "apihelp-query+allimages-example-recent": "Afficher une liste des fichiers récemment téléchargés semblable à [[Special:NewFiles]]",
+       "apihelp-query+allimages-example-recent": "Afficher une liste de fichiers récemment téléchargés, semblable à [[Special:NewFiles]].",
        "apihelp-query+allimages-example-mimetypes": "Afficher une liste de fichiers avec le type MIME <kbd>image/png</kbd> ou <kbd>image/gif</kbd>",
        "apihelp-query+allimages-example-generator": "Afficher l’information sur 4 fichiers commençant par la lettre <kbd>T</kbd>.",
        "apihelp-query+alllinks-description": "Énumérer tous les liens pointant vers un espace de noms donné.",
        "apihelp-query+alllinks-paramvalue-prop-title": "Ajoute le titre du lien.",
        "apihelp-query+alllinks-param-namespace": "L’espace de noms à énumérer.",
        "apihelp-query+alllinks-param-limit": "Combien d’éléments renvoyer au total.",
-       "apihelp-query+alllinks-param-dir": "La direction dans laquelle lister.",
-       "apihelp-query+alllinks-example-B": "Lister les titres liés, y compris les manquants, avec les IDs des pages d’où ils proviennent, en démarrant à <kbd>B</kbd>.",
+       "apihelp-query+alllinks-param-dir": "L'ordre dans lequel lister.",
+       "apihelp-query+alllinks-example-B": "Lister les titres liés, y compris ceux manquants, avec les IDs des pages d’où ils proviennent, en démarrant à <kbd>B</kbd>.",
        "apihelp-query+alllinks-example-unique": "Lister les titres liés uniques",
        "apihelp-query+alllinks-example-unique-generator": "Obtient tous les titres liés, en marquant les manquants",
        "apihelp-query+alllinks-example-generator": "Obtient les pages contenant les liens",
        "apihelp-query+allmessages-description": "Renvoyer les messages depuis ce site.",
        "apihelp-query+allmessages-param-messages": "Quels messages sortir. <kbd>*</kbd> (par défaut) signifie tous les messages.",
        "apihelp-query+allmessages-param-prop": "Quelles propriétés obtenir.",
-       "apihelp-query+allmessages-param-enableparser": "Si positionné pour activer l’analyseur, traitera en avance le wikitexte du message (substitution des mots magiques, gestion des modèles, etc.).",
+       "apihelp-query+allmessages-param-enableparser": "Positionner pour activer l’analyseur, traitera en avance le wikitexte du message (substitution des mots magiques, gestion des modèles, etc.).",
        "apihelp-query+allmessages-param-nocontent": "Si positionné, ne pas inclure le contenu des messages dans la sortie.",
        "apihelp-query+allmessages-param-includelocal": "Inclure aussi les messages locaux, c’est-à-dire les messages qui n’existent pas dans le logiciel mais dans l’espace de noms {{ns:MediaWiki}}.\nCela liste toutes les pages de l’espace de noms {{ns:MediaWiki}}, donc aussi celles qui ne sont pas vraiment des messages, telles que [[MediaWiki:Common.js|Common.js]].",
        "apihelp-query+allmessages-param-args": "Arguments à substituer dans le message.",
index ab409e2..e2e7ae6 100644 (file)
        "apiwarn-invalidcategory": "\"$1\" non é unha categoría.",
        "apiwarn-invalidtitle": "\"$1\" non é un título válido.",
        "apiwarn-notfile": "\"$1\" non é un ficheiro.",
+       "apiwarn-parse-nocontentmodel": "Non se proporcionou <var>title</var> nin <var>contentmodel</var>, asúmese $1.",
+       "apiwarn-tokennotallowed": "A acción \"$1\" non está permitida para o usuario actual.",
+       "apiwarn-toomanyvalues": "Demasiados valores para o parámetro <var>$1</var>: o límite é $2.",
+       "apiwarn-truncatedresult": "Truncouse este resultado porque doutra maneira sobrepasaría o límite de $1 bytes.",
+       "apiwarn-validationfailed-badpref": "non é unha preferencia válida.",
+       "apiwarn-validationfailed-cannotset": "non pode ser establecido por este módulo.",
+       "apiwarn-validationfailed": "Erro de validación de <kbd>$1</kbd>: $2",
+       "apiwarn-wgDebugAPI": "<strong>Aviso de seguridade</strong>: <var>$wgDebugAPI</var> está habilitado.",
        "api-feed-error-title": "Erro ($1)",
        "api-usage-docref": "Consulte $1 para ver o uso da API.",
        "api-exception-trace": "$1 en $2($3)\n$4",
index 49f8de7..3b00c1e 100644 (file)
        "api-help-permissions": "{{PLURAL:$1|権限}}:",
        "api-help-permissions-granted-to": "{{PLURAL:$1|権限を持つグループ}}: $2",
        "api-help-open-in-apisandbox": "<small>[サンドボックスで開く]</small>",
+       "apierror-missingparam": "パラメーター <var>$1</var> を設定してください。",
        "api-credits-header": "クレジット",
        "api-credits": "API の開発者:\n* Roan Kattouw (2007年9月-2009年の主任開発者)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (作成者、2006年9月-2007年9月の主任開発者)\n* Brad Jorsch (2013年-現在の主任開発者)\n\nコメント、提案、質問は mediawiki-api@lists.wikimedia.org にお送りください。\nバグはこちらへご報告ください: https://phabricator.wikimedia.org/"
 }
index 76db21e..6f271ec 100644 (file)
        "apihelp-edit-param-redirect": "자동으로 넘겨주기 처리하기.",
        "apihelp-edit-param-contentmodel": "새 콘텐츠의 콘텐츠 모델.",
        "apihelp-edit-example-edit": "문서 편집",
+       "apihelp-edit-example-undo": "자동 편집요약으로 13579판에서 13585판까지 되돌리기.",
        "apihelp-emailuser-description": "사용자에게 이메일을 보냅니다.",
        "apihelp-emailuser-param-target": "이메일을 받을 사용자.",
        "apihelp-emailuser-param-subject": "제목 헤더.",
        "apihelp-protect-description": "문서의 보호 수준을 변경합니다.",
        "apihelp-protect-param-reason": "보호 또는 보호 해제의 이유.",
        "apihelp-protect-example-protect": "문서 보호",
+       "apihelp-purge-description": "주어진 제목을 위한 캐시를 새로 고침.",
        "apihelp-purge-param-forcelinkupdate": "링크 테이블을 업데이트합니다.",
        "apihelp-query-param-prop": "조회된 페이지에 대해 가져올 속성입니다.",
        "apihelp-query-param-list": "가져올 목록입니다.",
index 1dfbcba..ba29748 100644 (file)
        "apihelp-protect-example-protect": "Zabezpiecz stronę",
        "apihelp-protect-example-unprotect": "Odbezpiecz stronę ustawiając ograniczenia na <kbd>all</kbd> (czyli każdy może wykonać działanie).",
        "apihelp-protect-example-unprotect2": "Odbezpiecz stronę ustawiając brak ograniczeń.",
-       "apihelp-purge-description": "Wyczyść pamięć podręczną dla stron o podanych tytułach.\n\nWymaga wysłania jako żądanie POST jeżeli użytkownik jest niezalogowany.",
+       "apihelp-purge-description": "Wyczyść pamięć podręczną dla stron o podanych tytułach.",
        "apihelp-purge-param-forcelinkupdate": "Uaktualnij tabele linków.",
        "apihelp-purge-param-forcerecursivelinkupdate": "Uaktualnij tabele linków włącznie z linkami dotyczącymi każdej strony wykorzystywanej jako szablon na tej stronie.",
        "apihelp-purge-example-simple": "Wyczyść strony <kbd>Main Page</kbd> i <kbd>API</kbd>.",
        "apihelp-query+blocks-paramvalue-prop-reason": "Dodaje powód zablokowania.",
        "apihelp-query+blocks-example-simple": "Listuj blokady.",
        "apihelp-query+categories-param-limit": "Liczba kategorii do zwrócenia.",
+       "apihelp-query+categoryinfo-description": "Zwraca informacje o danych kategoriach.",
        "apihelp-query+categorymembers-description": "Wszystkie strony w danej kategorii.",
        "apihelp-query+categorymembers-param-title": "Kategoria, której zawartość wymienić (wymagane). Musi zawierać prefiks <kbd>{{ns:category}}:</kbd>. Nie może być używany równocześnie z <var>$1pageid</var>.",
        "apihelp-query+categorymembers-param-pageid": "ID strony kategorii, z której wymienić strony. Nie może być użyty równocześnie z <var>$1title</var>.",
        "apihelp-query+protectedtitles-description": "Lista wszystkich tytułów zabezpieczonych przed tworzeniem.",
        "apihelp-query+protectedtitles-param-namespace": "Listuj tylko strony z tych przestrzeni nazw.",
        "apihelp-query+protectedtitles-param-limit": "Łączna liczba stron do zwrócenia.",
+       "apihelp-query+protectedtitles-paramvalue-prop-level": "Dodaje poziom zabezpieczeń.",
        "apihelp-query+protectedtitles-example-simple": "Lista chronionych nagłówków",
+       "apihelp-query+querypage-param-page": "Nazwa strony specjalnej. Należy pamiętać o wielkości liter.",
        "apihelp-query+querypage-param-limit": "Liczba zwracanych wyników.",
        "apihelp-query+random-param-namespace": "Zwraca strony tylko w tych przestrzeniach nazw.",
        "apihelp-query+recentchanges-param-user": "Listuj tylko zmiany dokonane przez tego użytkownika.",
        "apihelp-query+recentchanges-param-excludeuser": "Nie listuj zmian dokonanych przez tego użytkownika.",
        "apihelp-query+recentchanges-param-tag": "Pokazuj tylko zmiany oznaczone tym tagiem.",
+       "apihelp-query+recentchanges-paramvalue-prop-comment": "Dodaje komentarz do edycji.",
        "apihelp-query+recentchanges-example-simple": "Lista ostatnich zmian.",
        "apihelp-query+redirects-description": "Zwraca wszystkie przekierowania do danej strony.",
        "apihelp-query+redirects-paramvalue-prop-title": "Nazwa każdego przekierowania.",
        "apihelp-query+siteinfo-paramvalue-prop-general": "Ogólne informacje o systemie.",
        "apihelp-query+siteinfo-paramvalue-prop-namespaces": "Lista zarejestrowanych przestrzeni nazw i ich nazwy kanoniczne.",
        "apihelp-query+siteinfo-paramvalue-prop-namespacealiases": "Lista zarejestrowanych aliasów przestrzeni nazw.",
+       "apihelp-query+siteinfo-paramvalue-prop-magicwords": "Lista słów magicznych i ich aliasów.",
        "apihelp-query+siteinfo-param-numberingroup": "Wyświetla liczbę użytkowników w grupach użytkowników.",
        "apihelp-query+siteinfo-example-simple": "Pobierz informacje o stronie.",
        "apihelp-query+stashimageinfo-param-sessionkey": "Alias dla $1filekey, dla kompatybilności wstecznej.",
        "apihelp-query+watchlist-paramvalue-type-external": "Zmiany zewnętrzne.",
        "apihelp-resetpassword-description": "Wyślij użytkownikowi e-mail do resetowania hasła.",
        "apihelp-resetpassword-example-email": "Wyślij e-mail do resetowania hasła do wszystkich użytkowników posiadających adres <kbd>user@example.com</kbd>.",
+       "apihelp-setpagelanguage-description": "Zmień język strony.",
+       "apihelp-setpagelanguage-param-reason": "Powód zmiany.",
        "apihelp-stashedit-param-title": "Tytuł edytowanej strony.",
        "apihelp-stashedit-param-sectiontitle": "Tytuł nowej sekcji.",
        "apihelp-stashedit-param-text": "Zawartość strony.",
        "apihelp-upload-param-ignorewarnings": "Ignoruj wszystkie ostrzeżenia.",
        "apihelp-upload-param-file": "Zawartość pliku.",
        "apihelp-userrights-param-user": "Nazwa użytkownika.",
+       "apihelp-userrights-param-userid": "Identyfikator użytkownika.",
        "apihelp-userrights-param-add": "Dodaj użytkownika do tych grup.",
+       "apihelp-userrights-param-remove": "Usuń użytkownika z tych grup.",
        "apihelp-userrights-param-reason": "Powód zmiany.",
+       "apihelp-validatepassword-param-password": "Hasło do walidacji.",
        "apihelp-json-description": "Dane wyjściowe w formacie JSON.",
        "apihelp-jsonfm-description": "Dane wyjściowe w formacie JSON (prawidłowo wyświetlane w HTML).",
        "apihelp-php-description": "Dane wyjściowe w serializowany formacie PHP.",
        "apihelp-phpfm-description": "Dane wyjściowe w serializowanym formacie PHP (prawidłowo wyświetlane w HTML).",
        "apihelp-xml-description": "Dane wyjściowe w formacie XML.",
        "apihelp-xml-param-xslt": "Jeśli określony, dodaje podaną stronę jako arkusz styli XSL. Powinna to być strona wiki w przestrzeni nazw MediaWiki, której nazwa kończy się na <code>.xsl</code>.",
+       "apihelp-xml-param-includexmlnamespace": "Jeśli zaznaczono, dodaje przestrzeń nazw XML.",
        "apihelp-xmlfm-description": "Dane wyjściowe w formacie XML (prawidłowo wyświetlane w HTML).",
        "api-format-title": "Wynik MediaWiki API",
        "api-pageset-param-titles": "Lista tytułów, z którymi pracować.",
        "apierror-baddiff": "Różnicy wersji nie można odtworzyć. Jedna lub obie wersje nie istnieją, lub nie masz uprawnień do ich wyświetlenia.",
        "apierror-badgenerator-unknown": "Nieznany <kbd>generator=$1</kbd>.",
        "apierror-badip": "Parametr IP nie jest prawidłowy.",
+       "apierror-badparameter": "Nieprawidłowa wartość parametru <var>$1</var>.",
        "apierror-badquery": "Nieprawidłowe zapytanie.",
+       "apierror-badtoken": "Nieprawidłowy token CSRF.",
        "apierror-blockedfrommail": "Została Ci zablokowana możliwość wysyłania e-maili.",
        "apierror-blocked": "Została Ci zablokowana możliwość edycji.",
+       "apierror-cannotviewtitle": "Nie masz uprawnień do oglądania $1.",
        "apierror-cantblock": "Nie masz uprawnień do blokowania użytkowników.",
        "apierror-cantimport": "Nie masz uprawnień do importowania stron.",
        "apierror-cantsend": "Nie jesteś zalogowany, nie masz potwierdzonego adresu e-mail, albo nie masz prawa wysyłać e-maili do innych użytkowników, więc nie możesz wysłać wiadomości e-mail.",
+       "apierror-databaseerror": "[$1] Błąd zapytania do bazy danych.",
+       "apierror-exceptioncaught": "[$1] Stwierdzono wyjątek: $2",
        "apierror-filedoesnotexist": "Plik nie istnieje.",
+       "apierror-import-unknownerror": "Nieznany błąd podczas importowania: $1.",
        "apierror-integeroutofrange-abovebotmax": "Wartość <var>$1</var> dla botów i administratorów nie może przekraczać $2 (ustawiono $3).",
        "apierror-integeroutofrange-abovemax": "Wartość <var>$1</var> dla użytkowników nie może przekraczać $2 (ustawiono $3).",
        "apierror-integeroutofrange-belowminimum": "Wartość <var>$1</var> nie może być mniejsza niż $2 (ustawiono $3).",
+       "apierror-invalidcategory": "Wprowadzona nazwa kategorii jest nieprawidłowa.",
        "apierror-invalidlang": "Nieprawidłowy kod języka dla parametru <var>$1</var>.",
        "apierror-invalidparammix": "{{PLURAL:$2|Parametry}} $1 nie mogą być używane razem.",
        "apierror-invalidtitle": "Zły tytuł „$1”.",
        "apierror-invalidurlparam": "Nieprawidłowa wartość <var>$1urlparam</var> (<kbd>$2=$3</kbd>).",
+       "apierror-invaliduser": "Niepoprawna nazwa użytkownika „$1”.",
+       "apierror-invaliduserid": "Identyfikator użytkownika <var>$1</var> jest nieprawidłowy.",
+       "apierror-maxlag-generic": "Oczekiwania na serwer bazy danych: opóźnienie $1 {{PLURAL:$1|sekunda|sekundy|sekund}}.",
        "apierror-missingparam": "Parametr <var>$1</var> musi być podany.",
        "apierror-missingtitle": "Wybrana przez ciebie strona nie istnieje.",
        "apierror-missingtitle-byname": "Strona $1 nie istnieje.",
        "apierror-mustbeloggedin": "Musisz się zalogować, aby mieć możliwość $1.",
        "apierror-noedit-anon": "Niezarejestrowani użytkownicy nie mogą edytować stron.",
        "apierror-noedit": "Nie masz uprawnień do edytowania stron.",
+       "apierror-nosuchpageid": "Nie ma strony z identyfikatorem $1.",
+       "apierror-nosuchrevid": "Nie ma wersji z identyfikatorem $1.",
+       "apierror-nosuchsection": "Nie ma sekcji $1.",
        "apierror-permissiondenied": "Nie masz uprawnień do $1.",
        "apierror-permissiondenied-generic": "Brak dostępu.",
        "apierror-permissiondenied-unblock": "Nie masz uprawnień do odblokowania użytkowników.",
        "apierror-protect-invalidaction": "Nieprawidłowy rodzaj zabezpieczenia „$1”.",
        "apierror-protect-invalidlevel": "Nieprawidłowy poziom zabezpieczeń „$1”.",
+       "apierror-readonly": "Wiki jest teraz w trybie tylko do odczytu.",
+       "apierror-revwrongpage": "r$1 nie jest wersją strony $2.",
        "apierror-specialpage-cantexecute": "Nie masz uprawnień, aby zobaczyć wyniki tej strony specjalnej.",
        "apierror-stashwrongowner": "Nieprawidłowy właściciel: $1",
        "apierror-unknownerror-nocode": "Nieznany błąd.",
        "apiwarn-invalidcategory": "„$1” nie jest kategorią.",
        "apiwarn-invalidtitle": "„$1” nie jest poprawnym tytułem.",
        "apiwarn-notfile": "„$1” nie jest plikiem.",
+       "apiwarn-validationfailed": "Błąd walidacji dla <kbd>$1</kbd>: $2",
        "api-feed-error-title": "Błąd ($1)",
        "api-exception-trace": "$1 w $2($3)\n$4",
        "api-credits-header": "Twórcy",
index 8be41e0..7960775 100644 (file)
        "apihelp-login-param-name": "Имя участника.",
        "apihelp-login-param-password": "Пароль.",
        "apihelp-login-param-domain": "Домен (необязательно).",
+       "apihelp-login-example-gettoken": "Получить токен входа.",
        "apihelp-login-example-login": "Войти",
        "apihelp-logout-description": "Выйти и очистить данные сессии.",
        "apihelp-mergehistory-description": "Объединение историй правок",
index 0c4bd1c..acd844e 100644 (file)
@@ -14,7 +14,8 @@
                        "VickyC",
                        "Josve05a",
                        "Rockyfelle",
-                       "Macofe"
+                       "Macofe",
+                       "Magol"
                ]
        },
        "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Dokumentation]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api E-postlista]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-aviseringar]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R|Buggar & förslag]\n</div>\n<strong>Status:</strong> Alla funktioner som visas på denna sida borde fungera. API:et är dock fortfarande under aktiv utveckling och kan ändras när som helst. Prenumerera på [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/mediawiki-api-announce e-postlistan] för att få aviseringar om uppdateringar.\n\n<strong>Felaktiga förfrågningar:</strong> När felaktiga förfrågningar skickas till API:et skickas en HTTP-header med nyckeln \"MediaWiki-API-Error\" och sedan sätts både värdet på headern och den felkoden som returneras till samma värde. För mer information läs [[mw:API:Errors_and_warnings|API: Fel och varningar]].",
        "apihelp-main-param-smaxage": "Ange headervärdet <code>s-maxage</code> till så här många sekunder. Fel cachelagras aldrig.",
        "apihelp-main-param-maxage": "Ange headervärdet <code>max-age</code> till så här många sekunder. Fel cachelagras aldrig.",
        "apihelp-main-param-assert": "Bekräfta att användaren är inloggad om satt till <kbd>user</kbd>, eller har bot-användarrättigheter om satt till <kbd>bot</kbd>.",
+       "apihelp-main-param-assertuser": "Verifiera att den nuvarande användaren är den namngivne användaren.",
        "apihelp-main-param-requestid": "Alla värde som anges här kommer att inkluderas i svaret. Kan användas för att särskilja förfrågningar.",
        "apihelp-main-param-servedby": "Inkludera det värdnamn som besvarade förfrågan i resultatet.",
        "apihelp-main-param-curtimestamp": "Inkludera den aktuella tidsstämpeln i resultatet.",
+       "apihelp-main-param-responselanginfo": "Inkluderar de språk som används för <var>uselang</var> och <var>errorlang</var> i resultatet.",
        "apihelp-main-param-origin": "När API:et används genom en cross-domain AJAX-begäran (CORS), ange detta till den ursprungliga domänen. Detta måste inkluderas i alla pre-flight-begäran, och mpste därför vara en del av den begärda URI:n (inte i POST-datat). Detta måste överensstämma med en av källorna i headern <code>Origin</code> exakt, så den måste sättas till något i stil med <kbd>http://en.wikipedia.org</kbd> eller <kbd>https://meta.wikimedia.org</kbd>. Om denna parameter inte överensstämmer med headern <code>Origin</code>, returneras ett 403-svar. Om denna parameter överensstämmer med headern <code>Origin</code> och källan är vitlistad, sätts en <code>Access-Control-Allow-Origin</code>-header.",
        "apihelp-main-param-uselang": "Språk som ska användas för meddelandeöversättningar. En lista med koder kan hämtas från <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> med <kbd>siprop=languages</kbd>, eller ange <kbd>user</kbd> för att använda den aktuella användarens språkpreferenser, eller ange <kbd>content</kbd> för att använda innehållsspråket.",
        "apihelp-block-description": "Blockera en användare.",
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
deleted file mode 100644 (file)
index d567d8b..0000000
+++ /dev/null
@@ -1,1384 +0,0 @@
-<?php
-/**
- * This is the MS SQL Server Native database abstraction layer.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- * @author Joel Penner <a-joelpe at microsoft dot com>
- * @author Chris Pucci <a-cpucci at microsoft dot com>
- * @author Ryan Biesemeyer <v-ryanbi at microsoft dot com>
- * @author Ryan Schmidt <skizzerz at gmail dot com>
- */
-
-/**
- * @ingroup Database
- */
-class DatabaseMssql extends Database {
-       protected $mInsertId = null;
-       protected $mLastResult = null;
-       protected $mAffectedRows = null;
-       protected $mSubqueryId = 0;
-       protected $mScrollableCursor = true;
-       protected $mPrepareStatements = true;
-       protected $mBinaryColumnCache = null;
-       protected $mBitColumnCache = null;
-       protected $mIgnoreDupKeyErrors = false;
-       protected $mIgnoreErrors = [];
-
-       protected $mPort;
-
-       public function implicitGroupby() {
-               return false;
-       }
-
-       public function implicitOrderby() {
-               return false;
-       }
-
-       public function unionSupportsOrderAndLimit() {
-               return false;
-       }
-
-       /**
-        * Usually aborts on failure
-        * @param string $server
-        * @param string $user
-        * @param string $password
-        * @param string $dbName
-        * @throws DBConnectionError
-        * @return bool|resource|null
-        */
-       public function open( $server, $user, $password, $dbName ) {
-               # Test for driver support, to avoid suppressed fatal error
-               if ( !function_exists( 'sqlsrv_connect' ) ) {
-                       throw new DBConnectionError(
-                               $this,
-                               "Microsoft SQL Server Native (sqlsrv) functions missing.
-                               You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n"
-                       );
-               }
-
-               global $wgDBport, $wgDBWindowsAuthentication;
-
-               # e.g. the class is being loaded
-               if ( !strlen( $user ) ) {
-                       return null;
-               }
-
-               $this->close();
-               $this->mServer = $server;
-               $this->mPort = $wgDBport;
-               $this->mUser = $user;
-               $this->mPassword = $password;
-               $this->mDBname = $dbName;
-
-               $connectionInfo = [];
-
-               if ( $dbName ) {
-                       $connectionInfo['Database'] = $dbName;
-               }
-
-               // Decide which auth scenerio to use
-               // if we are using Windows auth, then don't add credentials to $connectionInfo
-               if ( !$wgDBWindowsAuthentication ) {
-                       $connectionInfo['UID'] = $user;
-                       $connectionInfo['PWD'] = $password;
-               }
-
-               MediaWiki\suppressWarnings();
-               $this->mConn = sqlsrv_connect( $server, $connectionInfo );
-               MediaWiki\restoreWarnings();
-
-               if ( $this->mConn === false ) {
-                       throw new DBConnectionError( $this, $this->lastError() );
-               }
-
-               $this->mOpened = true;
-
-               return $this->mConn;
-       }
-
-       /**
-        * Closes a database connection, if it is open
-        * Returns success, true if already closed
-        * @return bool
-        */
-       protected function closeConnection() {
-               return sqlsrv_close( $this->mConn );
-       }
-
-       /**
-        * @param bool|MssqlResultWrapper|resource $result
-        * @return bool|MssqlResultWrapper
-        */
-       protected function resultObject( $result ) {
-               if ( !$result ) {
-                       return false;
-               } elseif ( $result instanceof MssqlResultWrapper ) {
-                       return $result;
-               } elseif ( $result === true ) {
-                       // Successful write query
-                       return $result;
-               } else {
-                       return new MssqlResultWrapper( $this, $result );
-               }
-       }
-
-       /**
-        * @param string $sql
-        * @return bool|MssqlResult
-        * @throws DBUnexpectedError
-        */
-       protected function doQuery( $sql ) {
-               if ( $this->getFlag( DBO_DEBUG ) ) {
-                       wfDebug( "SQL: [$sql]\n" );
-               }
-               $this->offset = 0;
-
-               // several extensions seem to think that all databases support limits
-               // via LIMIT N after the WHERE clause, but  MSSQL uses SELECT TOP N,
-               // so to catch any of those extensions we'll do a quick check for a
-               // LIMIT clause and pass $sql through $this->LimitToTopN() which parses
-               // the LIMIT clause and passes the result to $this->limitResult();
-               if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
-                       // massage LIMIT -> TopN
-                       $sql = $this->LimitToTopN( $sql );
-               }
-
-               // MSSQL doesn't have EXTRACT(epoch FROM XXX)
-               if ( preg_match( '#\bEXTRACT\s*?\(\s*?EPOCH\s+FROM\b#i', $sql, $matches ) ) {
-                       // This is same as UNIX_TIMESTAMP, we need to calc # of seconds from 1970
-                       $sql = str_replace( $matches[0], "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),", $sql );
-               }
-
-               // perform query
-
-               // SQLSRV_CURSOR_STATIC is slower than SQLSRV_CURSOR_CLIENT_BUFFERED (one of the two is
-               // needed if we want to be able to seek around the result set), however CLIENT_BUFFERED
-               // has a bug in the sqlsrv driver where wchar_t types (such as nvarchar) that are empty
-               // strings make php throw a fatal error "Severe error translating Unicode"
-               if ( $this->mScrollableCursor ) {
-                       $scrollArr = [ 'Scrollable' => SQLSRV_CURSOR_STATIC ];
-               } else {
-                       $scrollArr = [];
-               }
-
-               if ( $this->mPrepareStatements ) {
-                       // we do prepare + execute so we can get its field metadata for later usage if desired
-                       $stmt = sqlsrv_prepare( $this->mConn, $sql, [], $scrollArr );
-                       $success = sqlsrv_execute( $stmt );
-               } else {
-                       $stmt = sqlsrv_query( $this->mConn, $sql, [], $scrollArr );
-                       $success = (bool)$stmt;
-               }
-
-               // Make a copy to ensure what we add below does not get reflected in future queries
-               $ignoreErrors = $this->mIgnoreErrors;
-
-               if ( $this->mIgnoreDupKeyErrors ) {
-                       // ignore duplicate key errors
-                       // this emulates INSERT IGNORE in MySQL
-                       $ignoreErrors[] = '2601'; // duplicate key error caused by unique index
-                       $ignoreErrors[] = '2627'; // duplicate key error caused by primary key
-                       $ignoreErrors[] = '3621'; // generic "the statement has been terminated" error
-               }
-
-               if ( $success === false ) {
-                       $errors = sqlsrv_errors();
-                       $success = true;
-
-                       foreach ( $errors as $err ) {
-                               if ( !in_array( $err['code'], $ignoreErrors ) ) {
-                                       $success = false;
-                                       break;
-                               }
-                       }
-
-                       if ( $success === false ) {
-                               return false;
-                       }
-               }
-               // remember number of rows affected
-               $this->mAffectedRows = sqlsrv_rows_affected( $stmt );
-
-               return $stmt;
-       }
-
-       public function freeResult( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               sqlsrv_free_stmt( $res );
-       }
-
-       /**
-        * @param MssqlResultWrapper $res
-        * @return stdClass
-        */
-       public function fetchObject( $res ) {
-               // $res is expected to be an instance of MssqlResultWrapper here
-               return $res->fetchObject();
-       }
-
-       /**
-        * @param MssqlResultWrapper $res
-        * @return array
-        */
-       public function fetchRow( $res ) {
-               return $res->fetchRow();
-       }
-
-       /**
-        * @param mixed $res
-        * @return int
-        */
-       public function numRows( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               $ret = sqlsrv_num_rows( $res );
-
-               if ( $ret === false ) {
-                       // we cannot get an amount of rows from this cursor type
-                       // has_rows returns bool true/false if the result has rows
-                       $ret = (int)sqlsrv_has_rows( $res );
-               }
-
-               return $ret;
-       }
-
-       /**
-        * @param mixed $res
-        * @return int
-        */
-       public function numFields( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return sqlsrv_num_fields( $res );
-       }
-
-       /**
-        * @param mixed $res
-        * @param int $n
-        * @return int
-        */
-       public function fieldName( $res, $n ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return sqlsrv_field_metadata( $res )[$n]['Name'];
-       }
-
-       /**
-        * This must be called after nextSequenceVal
-        * @return int|null
-        */
-       public function insertId() {
-               return $this->mInsertId;
-       }
-
-       /**
-        * @param MssqlResultWrapper $res
-        * @param int $row
-        * @return bool
-        */
-       public function dataSeek( $res, $row ) {
-               return $res->seek( $row );
-       }
-
-       /**
-        * @return string
-        */
-       public function lastError() {
-               $strRet = '';
-               $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
-               if ( $retErrors != null ) {
-                       foreach ( $retErrors as $arrError ) {
-                               $strRet .= $this->formatError( $arrError ) . "\n";
-                       }
-               } else {
-                       $strRet = "No errors found";
-               }
-
-               return $strRet;
-       }
-
-       /**
-        * @param array $err
-        * @return string
-        */
-       private function formatError( $err ) {
-               return '[SQLSTATE ' . $err['SQLSTATE'] . '][Error Code ' . $err['code'] . ']' . $err['message'];
-       }
-
-       /**
-        * @return string|int
-        */
-       public function lastErrno() {
-               $err = sqlsrv_errors( SQLSRV_ERR_ALL );
-               if ( $err !== null && isset( $err[0] ) ) {
-                       return $err[0]['code'];
-               } else {
-                       return 0;
-               }
-       }
-
-       /**
-        * @return int
-        */
-       public function affectedRows() {
-               return $this->mAffectedRows;
-       }
-
-       /**
-        * SELECT wrapper
-        *
-        * @param mixed $table Array or string, table name(s) (prefix auto-added)
-        * @param mixed $vars Array or string, field name(s) to be retrieved
-        * @param mixed $conds Array or string, condition(s) for WHERE
-        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        * @param array $options Associative array of options (e.g.
-        *   [ 'GROUP BY' => 'page_title' ]), see Database::makeSelectOptions
-        *   code for list of supported stuff
-        * @param array $join_conds Associative array of table join conditions
-        *   (optional) (e.g. [ 'page' => [ 'LEFT JOIN','page_latest=rev_id' ] ]
-        * @return mixed Database result resource (feed to Database::fetchObject
-        *   or whatever), or false on failure
-        * @throws DBQueryError
-        * @throws DBUnexpectedError
-        * @throws Exception
-        */
-       public function select( $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
-       ) {
-               $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
-               if ( isset( $options['EXPLAIN'] ) ) {
-                       try {
-                               $this->mScrollableCursor = false;
-                               $this->mPrepareStatements = false;
-                               $this->query( "SET SHOWPLAN_ALL ON" );
-                               $ret = $this->query( $sql, $fname );
-                               $this->query( "SET SHOWPLAN_ALL OFF" );
-                       } catch ( DBQueryError $dqe ) {
-                               if ( isset( $options['FOR COUNT'] ) ) {
-                                       // likely don't have privs for SHOWPLAN, so run a select count instead
-                                       $this->query( "SET SHOWPLAN_ALL OFF" );
-                                       unset( $options['EXPLAIN'] );
-                                       $ret = $this->select(
-                                               $table,
-                                               'COUNT(*) AS EstimateRows',
-                                               $conds,
-                                               $fname,
-                                               $options,
-                                               $join_conds
-                                       );
-                               } else {
-                                       // someone actually wanted the query plan instead of an est row count
-                                       // let them know of the error
-                                       $this->mScrollableCursor = true;
-                                       $this->mPrepareStatements = true;
-                                       throw $dqe;
-                               }
-                       }
-                       $this->mScrollableCursor = true;
-                       $this->mPrepareStatements = true;
-                       return $ret;
-               }
-               return $this->query( $sql, $fname );
-       }
-
-       /**
-        * SELECT wrapper
-        *
-        * @param mixed $table Array or string, table name(s) (prefix auto-added)
-        * @param mixed $vars Array or string, field name(s) to be retrieved
-        * @param mixed $conds Array or string, condition(s) for WHERE
-        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
-        * @param array $options Associative array of options (e.g. [ 'GROUP BY' => 'page_title' ]),
-        *   see Database::makeSelectOptions code for list of supported stuff
-        * @param array $join_conds Associative array of table join conditions (optional)
-        *    (e.g. [ 'page' => [ 'LEFT JOIN','page_latest=rev_id' ] ]
-        * @return string The SQL text
-        */
-       public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
-               $options = [], $join_conds = []
-       ) {
-               if ( isset( $options['EXPLAIN'] ) ) {
-                       unset( $options['EXPLAIN'] );
-               }
-
-               $sql = parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
-
-               // try to rewrite aggregations of bit columns (currently MAX and MIN)
-               if ( strpos( $sql, 'MAX(' ) !== false || strpos( $sql, 'MIN(' ) !== false ) {
-                       $bitColumns = [];
-                       if ( is_array( $table ) ) {
-                               foreach ( $table as $t ) {
-                                       $bitColumns += $this->getBitColumns( $this->tableName( $t ) );
-                               }
-                       } else {
-                               $bitColumns = $this->getBitColumns( $this->tableName( $table ) );
-                       }
-
-                       foreach ( $bitColumns as $col => $info ) {
-                               $replace = [
-                                       "MAX({$col})" => "MAX(CAST({$col} AS tinyint))",
-                                       "MIN({$col})" => "MIN(CAST({$col} AS tinyint))",
-                               ];
-                               $sql = str_replace( array_keys( $replace ), array_values( $replace ), $sql );
-                       }
-               }
-
-               return $sql;
-       }
-
-       public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
-               $fname = __METHOD__
-       ) {
-               $this->mScrollableCursor = false;
-               try {
-                       parent::deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname );
-               } catch ( Exception $e ) {
-                       $this->mScrollableCursor = true;
-                       throw $e;
-               }
-               $this->mScrollableCursor = true;
-       }
-
-       public function delete( $table, $conds, $fname = __METHOD__ ) {
-               $this->mScrollableCursor = false;
-               try {
-                       parent::delete( $table, $conds, $fname );
-               } catch ( Exception $e ) {
-                       $this->mScrollableCursor = true;
-                       throw $e;
-               }
-               $this->mScrollableCursor = true;
-       }
-
-       /**
-        * Estimate rows in dataset
-        * Returns estimated count, based on SHOWPLAN_ALL output
-        * This is not necessarily an accurate estimate, so use sparingly
-        * Returns -1 if count cannot be found
-        * Takes same arguments as Database::select()
-        * @param string $table
-        * @param string $vars
-        * @param string $conds
-        * @param string $fname
-        * @param array $options
-        * @return int
-        */
-       public function estimateRowCount( $table, $vars = '*', $conds = '',
-               $fname = __METHOD__, $options = []
-       ) {
-               // http://msdn2.microsoft.com/en-us/library/aa259203.aspx
-               $options['EXPLAIN'] = true;
-               $options['FOR COUNT'] = true;
-               $res = $this->select( $table, $vars, $conds, $fname, $options );
-
-               $rows = -1;
-               if ( $res ) {
-                       $row = $this->fetchRow( $res );
-
-                       if ( isset( $row['EstimateRows'] ) ) {
-                               $rows = (int)$row['EstimateRows'];
-                       }
-               }
-
-               return $rows;
-       }
-
-       /**
-        * Returns information about an index
-        * If errors are explicitly ignored, returns NULL on failure
-        * @param string $table
-        * @param string $index
-        * @param string $fname
-        * @return array|bool|null
-        */
-       public function indexInfo( $table, $index, $fname = __METHOD__ ) {
-               # This does not return the same info as MYSQL would, but that's OK
-               # because MediaWiki never uses the returned value except to check for
-               # the existence of indexes.
-               $sql = "sp_helpindex '" . $this->tableName( $table ) . "'";
-               $res = $this->query( $sql, $fname );
-
-               if ( !$res ) {
-                       return null;
-               }
-
-               $result = [];
-               foreach ( $res as $row ) {
-                       if ( $row->index_name == $index ) {
-                               $row->Non_unique = !stristr( $row->index_description, "unique" );
-                               $cols = explode( ", ", $row->index_keys );
-                               foreach ( $cols as $col ) {
-                                       $row->Column_name = trim( $col );
-                                       $result[] = clone $row;
-                               }
-                       } elseif ( $index == 'PRIMARY' && stristr( $row->index_description, 'PRIMARY' ) ) {
-                               $row->Non_unique = 0;
-                               $cols = explode( ", ", $row->index_keys );
-                               foreach ( $cols as $col ) {
-                                       $row->Column_name = trim( $col );
-                                       $result[] = clone $row;
-                               }
-                       }
-               }
-
-               return empty( $result ) ? false : $result;
-       }
-
-       /**
-        * INSERT wrapper, inserts an array into a table
-        *
-        * $arrToInsert may be a single associative array, or an array of these with numeric keys, for
-        * multi-row insert.
-        *
-        * Usually aborts on failure
-        * If errors are explicitly ignored, returns success
-        * @param string $table
-        * @param array $arrToInsert
-        * @param string $fname
-        * @param array $options
-        * @return bool
-        * @throws Exception
-        */
-       public function insert( $table, $arrToInsert, $fname = __METHOD__, $options = [] ) {
-               # No rows to insert, easy just return now
-               if ( !count( $arrToInsert ) ) {
-                       return true;
-               }
-
-               if ( !is_array( $options ) ) {
-                       $options = [ $options ];
-               }
-
-               $table = $this->tableName( $table );
-
-               if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) { // Not multi row
-                       $arrToInsert = [ 0 => $arrToInsert ]; // make everything multi row compatible
-               }
-
-               // We know the table we're inserting into, get its identity column
-               $identity = null;
-               // strip matching square brackets and the db/schema from table name
-               $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
-               $tableRaw = array_pop( $tableRawArr );
-               $res = $this->doQuery(
-                       "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS " .
-                               "WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'"
-               );
-               if ( $res && sqlsrv_has_rows( $res ) ) {
-                       // There is an identity for this table.
-                       $identityArr = sqlsrv_fetch_array( $res, SQLSRV_FETCH_ASSOC );
-                       $identity = array_pop( $identityArr );
-               }
-               sqlsrv_free_stmt( $res );
-
-               // Determine binary/varbinary fields so we can encode data as a hex string like 0xABCDEF
-               $binaryColumns = $this->getBinaryColumns( $table );
-
-               // INSERT IGNORE is not supported by SQL Server
-               // remove IGNORE from options list and set ignore flag to true
-               if ( in_array( 'IGNORE', $options ) ) {
-                       $options = array_diff( $options, [ 'IGNORE' ] );
-                       $this->mIgnoreDupKeyErrors = true;
-               }
-
-               foreach ( $arrToInsert as $a ) {
-                       // start out with empty identity column, this is so we can return
-                       // it as a result of the INSERT logic
-                       $sqlPre = '';
-                       $sqlPost = '';
-                       $identityClause = '';
-
-                       // if we have an identity column
-                       if ( $identity ) {
-                               // iterate through
-                               foreach ( $a as $k => $v ) {
-                                       if ( $k == $identity ) {
-                                               if ( !is_null( $v ) ) {
-                                                       // there is a value being passed to us,
-                                                       // we need to turn on and off inserted identity
-                                                       $sqlPre = "SET IDENTITY_INSERT $table ON;";
-                                                       $sqlPost = ";SET IDENTITY_INSERT $table OFF;";
-                                               } else {
-                                                       // we can't insert NULL into an identity column,
-                                                       // so remove the column from the insert.
-                                                       unset( $a[$k] );
-                                               }
-                                       }
-                               }
-
-                               // we want to output an identity column as result
-                               $identityClause = "OUTPUT INSERTED.$identity ";
-                       }
-
-                       $keys = array_keys( $a );
-
-                       // Build the actual query
-                       $sql = $sqlPre . 'INSERT ' . implode( ' ', $options ) .
-                               " INTO $table (" . implode( ',', $keys ) . ") $identityClause VALUES (";
-
-                       $first = true;
-                       foreach ( $a as $key => $value ) {
-                               if ( isset( $binaryColumns[$key] ) ) {
-                                       $value = new MssqlBlob( $value );
-                               }
-                               if ( $first ) {
-                                       $first = false;
-                               } else {
-                                       $sql .= ',';
-                               }
-                               if ( is_null( $value ) ) {
-                                       $sql .= 'null';
-                               } elseif ( is_array( $value ) || is_object( $value ) ) {
-                                       if ( is_object( $value ) && $value instanceof Blob ) {
-                                               $sql .= $this->addQuotes( $value );
-                                       } else {
-                                               $sql .= $this->addQuotes( serialize( $value ) );
-                                       }
-                               } else {
-                                       $sql .= $this->addQuotes( $value );
-                               }
-                       }
-                       $sql .= ')' . $sqlPost;
-
-                       // Run the query
-                       $this->mScrollableCursor = false;
-                       try {
-                               $ret = $this->query( $sql );
-                       } catch ( Exception $e ) {
-                               $this->mScrollableCursor = true;
-                               $this->mIgnoreDupKeyErrors = false;
-                               throw $e;
-                       }
-                       $this->mScrollableCursor = true;
-
-                       if ( !is_null( $identity ) ) {
-                               // then we want to get the identity column value we were assigned and save it off
-                               $row = $ret->fetchObject();
-                               if ( is_object( $row ) ) {
-                                       $this->mInsertId = $row->$identity;
-
-                                       // it seems that mAffectedRows is -1 sometimes when OUTPUT INSERTED.identity is used
-                                       // if we got an identity back, we know for sure a row was affected, so adjust that here
-                                       if ( $this->mAffectedRows == -1 ) {
-                                               $this->mAffectedRows = 1;
-                                       }
-                               }
-                       }
-               }
-               $this->mIgnoreDupKeyErrors = false;
-               return $ret;
-       }
-
-       /**
-        * INSERT SELECT wrapper
-        * $varMap must be an associative array of the form [ 'dest1' => 'source1', ... ]
-        * Source items may be literals rather than field names, but strings should
-        * be quoted with Database::addQuotes().
-        * @param string $destTable
-        * @param array|string $srcTable May be an array of tables.
-        * @param array $varMap
-        * @param array $conds May be "*" to copy the whole table.
-        * @param string $fname
-        * @param array $insertOptions
-        * @param array $selectOptions
-        * @return null|ResultWrapper
-        * @throws Exception
-        */
-       public function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
-               $insertOptions = [], $selectOptions = []
-       ) {
-               $this->mScrollableCursor = false;
-               try {
-                       $ret = parent::nativeInsertSelect(
-                               $destTable,
-                               $srcTable,
-                               $varMap,
-                               $conds,
-                               $fname,
-                               $insertOptions,
-                               $selectOptions
-                       );
-               } catch ( Exception $e ) {
-                       $this->mScrollableCursor = true;
-                       throw $e;
-               }
-               $this->mScrollableCursor = true;
-
-               return $ret;
-       }
-
-       /**
-        * UPDATE wrapper. Takes a condition array and a SET array.
-        *
-        * @param string $table Name of the table to UPDATE. This will be passed through
-        *                Database::tableName().
-        *
-        * @param array $values An array of values to SET. For each array element,
-        *                the key gives the field name, and the value gives the data
-        *                to set that field to. The data will be quoted by
-        *                Database::addQuotes().
-        *
-        * @param array $conds An array of conditions (WHERE). See
-        *                Database::select() for the details of the format of
-        *                condition arrays. Use '*' to update all rows.
-        *
-        * @param string $fname The function name of the caller (from __METHOD__),
-        *                for logging and profiling.
-        *
-        * @param array $options An array of UPDATE options, can be:
-        *                   - IGNORE: Ignore unique key conflicts
-        *                   - LOW_PRIORITY: MySQL-specific, see MySQL manual.
-        * @return bool
-        * @throws DBUnexpectedError
-        * @throws Exception
-        */
-       function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
-               $table = $this->tableName( $table );
-               $binaryColumns = $this->getBinaryColumns( $table );
-
-               $opts = $this->makeUpdateOptions( $options );
-               $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET, $binaryColumns );
-
-               if ( $conds !== [] && $conds !== '*' ) {
-                       $sql .= " WHERE " . $this->makeList( $conds, LIST_AND, $binaryColumns );
-               }
-
-               $this->mScrollableCursor = false;
-               try {
-                       $this->query( $sql );
-               } catch ( Exception $e ) {
-                       $this->mScrollableCursor = true;
-                       throw $e;
-               }
-               $this->mScrollableCursor = true;
-               return true;
-       }
-
-       /**
-        * Makes an encoded list of strings from an array
-        * @param array $a Containing the data
-        * @param int $mode Constant
-        *      - LIST_COMMA:          comma separated, no field names
-        *      - LIST_AND:            ANDed WHERE clause (without the WHERE). See
-        *        the documentation for $conds in Database::select().
-        *      - LIST_OR:             ORed WHERE clause (without the WHERE)
-        *      - LIST_SET:            comma separated with field names, like a SET clause
-        *      - LIST_NAMES:          comma separated field names
-        * @param array $binaryColumns Contains a list of column names that are binary types
-        *      This is a custom parameter only present for MS SQL.
-        *
-        * @throws DBUnexpectedError
-        * @return string
-        */
-       public function makeList( $a, $mode = LIST_COMMA, $binaryColumns = [] ) {
-               if ( !is_array( $a ) ) {
-                       throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
-               }
-
-               if ( $mode != LIST_NAMES ) {
-                       // In MS SQL, values need to be specially encoded when they are
-                       // inserted into binary fields. Perform this necessary encoding
-                       // for the specified set of columns.
-                       foreach ( array_keys( $a ) as $field ) {
-                               if ( !isset( $binaryColumns[$field] ) ) {
-                                       continue;
-                               }
-
-                               if ( is_array( $a[$field] ) ) {
-                                       foreach ( $a[$field] as &$v ) {
-                                               $v = new MssqlBlob( $v );
-                                       }
-                                       unset( $v );
-                               } else {
-                                       $a[$field] = new MssqlBlob( $a[$field] );
-                               }
-                       }
-               }
-
-               return parent::makeList( $a, $mode );
-       }
-
-       /**
-        * @param string $table
-        * @param string $field
-        * @return int Returns the size of a text field, or -1 for "unlimited"
-        */
-       public function textFieldSize( $table, $field ) {
-               $table = $this->tableName( $table );
-               $sql = "SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
-                       WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
-               $res = $this->query( $sql );
-               $row = $this->fetchRow( $res );
-               $size = -1;
-               if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) {
-                       $size = $row['CHARACTER_MAXIMUM_LENGTH'];
-               }
-
-               return $size;
-       }
-
-       /**
-        * Construct a LIMIT query with optional offset
-        * This is used for query pages
-        *
-        * @param string $sql SQL query we will append the limit too
-        * @param int $limit The SQL limit
-        * @param bool|int $offset The SQL offset (default false)
-        * @return array|string
-        * @throws DBUnexpectedError
-        */
-       public function limitResult( $sql, $limit, $offset = false ) {
-               if ( $offset === false || $offset == 0 ) {
-                       if ( strpos( $sql, "SELECT" ) === false ) {
-                               return "TOP {$limit} " . $sql;
-                       } else {
-                               return preg_replace( '/\bSELECT(\s+DISTINCT)?\b/Dsi',
-                                       'SELECT$1 TOP ' . $limit, $sql, 1 );
-                       }
-               } else {
-                       // This one is fun, we need to pull out the select list as well as any ORDER BY clause
-                       $select = $orderby = [];
-                       $s1 = preg_match( '#SELECT\s+(.+?)\s+FROM#Dis', $sql, $select );
-                       $s2 = preg_match( '#(ORDER BY\s+.+?)(\s*FOR XML .*)?$#Dis', $sql, $orderby );
-                       $overOrder = $postOrder = '';
-                       $first = $offset + 1;
-                       $last = $offset + $limit;
-                       $sub1 = 'sub_' . $this->mSubqueryId;
-                       $sub2 = 'sub_' . ( $this->mSubqueryId + 1 );
-                       $this->mSubqueryId += 2;
-                       if ( !$s1 ) {
-                               // wat
-                               throw new DBUnexpectedError( $this, "Attempting to LIMIT a non-SELECT query\n" );
-                       }
-                       if ( !$s2 ) {
-                               // no ORDER BY
-                               $overOrder = 'ORDER BY (SELECT 1)';
-                       } else {
-                               if ( !isset( $orderby[2] ) || !$orderby[2] ) {
-                                       // don't need to strip it out if we're using a FOR XML clause
-                                       $sql = str_replace( $orderby[1], '', $sql );
-                               }
-                               $overOrder = $orderby[1];
-                               $postOrder = ' ' . $overOrder;
-                       }
-                       $sql = "SELECT {$select[1]}
-                                       FROM (
-                                               SELECT ROW_NUMBER() OVER({$overOrder}) AS rowNumber, *
-                                               FROM ({$sql}) {$sub1}
-                                       ) {$sub2}
-                                       WHERE rowNumber BETWEEN {$first} AND {$last}{$postOrder}";
-
-                       return $sql;
-               }
-       }
-
-       /**
-        * If there is a limit clause, parse it, strip it, and pass the remaining
-        * SQL through limitResult() with the appropriate parameters. Not the
-        * prettiest solution, but better than building a whole new parser. This
-        * exists becase there are still too many extensions that don't use dynamic
-        * sql generation.
-        *
-        * @param string $sql
-        * @return array|mixed|string
-        */
-       public function LimitToTopN( $sql ) {
-               // Matches: LIMIT {[offset,] row_count | row_count OFFSET offset}
-               $pattern = '/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
-               if ( preg_match( $pattern, $sql, $matches ) ) {
-                       $row_count = $matches[4];
-                       $offset = $matches[3] ?: $matches[6] ?: false;
-
-                       // strip the matching LIMIT clause out
-                       $sql = str_replace( $matches[0], '', $sql );
-
-                       return $this->limitResult( $sql, $row_count, $offset );
-               }
-
-               return $sql;
-       }
-
-       /**
-        * @return string Wikitext of a link to the server software's web site
-        */
-       public function getSoftwareLink() {
-               return "[{{int:version-db-mssql-url}} MS SQL Server]";
-       }
-
-       /**
-        * @return string Version information from the database
-        */
-       public function getServerVersion() {
-               $server_info = sqlsrv_server_info( $this->mConn );
-               $version = 'Error';
-               if ( isset( $server_info['SQLServerVersion'] ) ) {
-                       $version = $server_info['SQLServerVersion'];
-               }
-
-               return $version;
-       }
-
-       /**
-        * @param string $table
-        * @param string $fname
-        * @return bool
-        */
-       public function tableExists( $table, $fname = __METHOD__ ) {
-               list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
-
-               if ( $db !== false ) {
-                       // remote database
-                       wfDebug( "Attempting to call tableExists on a remote table" );
-                       return false;
-               }
-
-               if ( $schema === false ) {
-                       global $wgDBmwschema;
-                       $schema = $wgDBmwschema;
-               }
-
-               $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
-                       WHERE TABLE_TYPE = 'BASE TABLE'
-                       AND TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table'" );
-
-               if ( $res->numRows() ) {
-                       return true;
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * Query whether a given column exists in the mediawiki schema
-        * @param string $table
-        * @param string $field
-        * @param string $fname
-        * @return bool
-        */
-       public function fieldExists( $table, $field, $fname = __METHOD__ ) {
-               list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
-
-               if ( $db !== false ) {
-                       // remote database
-                       wfDebug( "Attempting to call fieldExists on a remote table" );
-                       return false;
-               }
-
-               $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
-                       WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
-
-               if ( $res->numRows() ) {
-                       return true;
-               } else {
-                       return false;
-               }
-       }
-
-       public function fieldInfo( $table, $field ) {
-               list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
-
-               if ( $db !== false ) {
-                       // remote database
-                       wfDebug( "Attempting to call fieldInfo on a remote table" );
-                       return false;
-               }
-
-               $res = $this->query( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS
-                       WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
-
-               $meta = $res->fetchRow();
-               if ( $meta ) {
-                       return new MssqlField( $meta );
-               }
-
-               return false;
-       }
-
-       /**
-        * Begin a transaction, committing any previously open transaction
-        * @param string $fname
-        */
-       protected function doBegin( $fname = __METHOD__ ) {
-               sqlsrv_begin_transaction( $this->mConn );
-               $this->mTrxLevel = 1;
-       }
-
-       /**
-        * End a transaction
-        * @param string $fname
-        */
-       protected function doCommit( $fname = __METHOD__ ) {
-               sqlsrv_commit( $this->mConn );
-               $this->mTrxLevel = 0;
-       }
-
-       /**
-        * Rollback a transaction.
-        * No-op on non-transactional databases.
-        * @param string $fname
-        */
-       protected function doRollback( $fname = __METHOD__ ) {
-               sqlsrv_rollback( $this->mConn );
-               $this->mTrxLevel = 0;
-       }
-
-       /**
-        * Escapes a identifier for use inm SQL.
-        * Throws an exception if it is invalid.
-        * Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
-        * @param string $identifier
-        * @throws InvalidArgumentException
-        * @return string
-        */
-       private function escapeIdentifier( $identifier ) {
-               if ( strlen( $identifier ) == 0 ) {
-                       throw new InvalidArgumentException( "An identifier must not be empty" );
-               }
-               if ( strlen( $identifier ) > 128 ) {
-                       throw new InvalidArgumentException( "The identifier '$identifier' is too long (max. 128)" );
-               }
-               if ( ( strpos( $identifier, '[' ) !== false )
-                       || ( strpos( $identifier, ']' ) !== false )
-               ) {
-                       // It may be allowed if you quoted with double quotation marks, but
-                       // that would break if QUOTED_IDENTIFIER is OFF
-                       throw new InvalidArgumentException( "Square brackets are not allowed in '$identifier'" );
-               }
-
-               return "[$identifier]";
-       }
-
-       /**
-        * @param string $s
-        * @return string
-        */
-       public function strencode( $s ) {
-               // Should not be called by us
-
-               return str_replace( "'", "''", $s );
-       }
-
-       /**
-        * @param string|int|null|bool|Blob $s
-        * @return string|int
-        */
-       public function addQuotes( $s ) {
-               if ( $s instanceof MssqlBlob ) {
-                       return $s->fetch();
-               } elseif ( $s instanceof Blob ) {
-                       // this shouldn't really ever be called, but it's here if needed
-                       // (and will quite possibly make the SQL error out)
-                       $blob = new MssqlBlob( $s->fetch() );
-                       return $blob->fetch();
-               } else {
-                       if ( is_bool( $s ) ) {
-                               $s = $s ? 1 : 0;
-                       }
-                       return parent::addQuotes( $s );
-               }
-       }
-
-       /**
-        * @param string $s
-        * @return string
-        */
-       public function addIdentifierQuotes( $s ) {
-               // http://msdn.microsoft.com/en-us/library/aa223962.aspx
-               return '[' . $s . ']';
-       }
-
-       /**
-        * @param string $name
-        * @return bool
-        */
-       public function isQuotedIdentifier( $name ) {
-               return strlen( $name ) && $name[0] == '[' && substr( $name, -1, 1 ) == ']';
-       }
-
-       /**
-        * MS SQL supports more pattern operators than other databases (ex: [,],^)
-        *
-        * @param string $s
-        * @return string
-        */
-       protected function escapeLikeInternal( $s ) {
-               return addcslashes( $s, '\%_[]^' );
-       }
-
-       /**
-        * MS SQL requires specifying the escape character used in a LIKE query
-        * or using Square brackets to surround characters that are to be escaped
-        * https://msdn.microsoft.com/en-us/library/ms179859.aspx
-        * Here we take the Specify-Escape-Character approach since it's less
-        * invasive, renders a query that is closer to other DB's and better at
-        * handling square bracket escaping
-        *
-        * @return string Fully built LIKE statement
-        */
-       public function buildLike() {
-               $params = func_get_args();
-               if ( count( $params ) > 0 && is_array( $params[0] ) ) {
-                       $params = $params[0];
-               }
-
-               return parent::buildLike( $params ) . " ESCAPE '\' ";
-       }
-
-       /**
-        * @param string $db
-        * @return bool
-        */
-       public function selectDB( $db ) {
-               try {
-                       $this->mDBname = $db;
-                       $this->query( "USE $db" );
-                       return true;
-               } catch ( Exception $e ) {
-                       return false;
-               }
-       }
-
-       /**
-        * @param array $options An associative array of options to be turned into
-        *   an SQL query, valid keys are listed in the function.
-        * @return array
-        */
-       public function makeSelectOptions( $options ) {
-               $tailOpts = '';
-               $startOpts = '';
-
-               $noKeyOptions = [];
-               foreach ( $options as $key => $option ) {
-                       if ( is_numeric( $key ) ) {
-                               $noKeyOptions[$option] = true;
-                       }
-               }
-
-               $tailOpts .= $this->makeGroupByWithHaving( $options );
-
-               $tailOpts .= $this->makeOrderBy( $options );
-
-               if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
-                       $startOpts .= 'DISTINCT';
-               }
-
-               if ( isset( $noKeyOptions['FOR XML'] ) ) {
-                       // used in group concat field emulation
-                       $tailOpts .= " FOR XML PATH('')";
-               }
-
-               // we want this to be compatible with the output of parent::makeSelectOptions()
-               return [ $startOpts, '', $tailOpts, '', '' ];
-       }
-
-       /**
-        * Get the type of the DBMS, as it appears in $wgDBtype.
-        * @return string
-        */
-       public function getType() {
-               return 'mssql';
-       }
-
-       /**
-        * @param array $stringList
-        * @return string
-        */
-       public function buildConcat( $stringList ) {
-               return implode( ' + ', $stringList );
-       }
-
-       /**
-        * Build a GROUP_CONCAT or equivalent statement for a query.
-        * MS SQL doesn't have GROUP_CONCAT so we emulate it with other stuff (and boy is it nasty)
-        *
-        * This is useful for combining a field for several rows into a single string.
-        * NULL values will not appear in the output, duplicated values will appear,
-        * and the resulting delimiter-separated values have no defined sort order.
-        * Code using the results may need to use the PHP unique() or sort() methods.
-        *
-        * @param string $delim Glue to bind the results together
-        * @param string|array $table Table name
-        * @param string $field Field name
-        * @param string|array $conds Conditions
-        * @param string|array $join_conds Join conditions
-        * @return string SQL text
-        * @since 1.23
-        */
-       public function buildGroupConcatField( $delim, $table, $field, $conds = '',
-               $join_conds = []
-       ) {
-               $gcsq = 'gcsq_' . $this->mSubqueryId;
-               $this->mSubqueryId++;
-
-               $delimLen = strlen( $delim );
-               $fld = "{$field} + {$this->addQuotes( $delim )}";
-               $sql = "(SELECT LEFT({$field}, LEN({$field}) - {$delimLen}) FROM ("
-                       . $this->selectSQLText( $table, $fld, $conds, null, [ 'FOR XML' ], $join_conds )
-                       . ") {$gcsq} ({$field}))";
-
-               return $sql;
-       }
-
-       /**
-        * Returns an associative array for fields that are of type varbinary, binary, or image
-        * $table can be either a raw table name or passed through tableName() first
-        * @param string $table
-        * @return array
-        */
-       private function getBinaryColumns( $table ) {
-               $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
-               $tableRaw = array_pop( $tableRawArr );
-
-               if ( $this->mBinaryColumnCache === null ) {
-                       $this->populateColumnCaches();
-               }
-
-               return isset( $this->mBinaryColumnCache[$tableRaw] )
-                       ? $this->mBinaryColumnCache[$tableRaw]
-                       : [];
-       }
-
-       /**
-        * @param string $table
-        * @return array
-        */
-       private function getBitColumns( $table ) {
-               $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
-               $tableRaw = array_pop( $tableRawArr );
-
-               if ( $this->mBitColumnCache === null ) {
-                       $this->populateColumnCaches();
-               }
-
-               return isset( $this->mBitColumnCache[$tableRaw] )
-                       ? $this->mBitColumnCache[$tableRaw]
-                       : [];
-       }
-
-       private function populateColumnCaches() {
-               $res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
-                       [
-                               'TABLE_CATALOG' => $this->mDBname,
-                               'TABLE_SCHEMA' => $this->mSchema,
-                               'DATA_TYPE' => [ 'varbinary', 'binary', 'image', 'bit' ]
-                       ] );
-
-               $this->mBinaryColumnCache = [];
-               $this->mBitColumnCache = [];
-               foreach ( $res as $row ) {
-                       if ( $row->DATA_TYPE == 'bit' ) {
-                               $this->mBitColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
-                       } else {
-                               $this->mBinaryColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
-                       }
-               }
-       }
-
-       /**
-        * @param string $name
-        * @param string $format
-        * @return string
-        */
-       function tableName( $name, $format = 'quoted' ) {
-               # Replace reserved words with better ones
-               switch ( $name ) {
-                       case 'user':
-                               return $this->realTableName( 'mwuser', $format );
-                       default:
-                               return $this->realTableName( $name, $format );
-               }
-       }
-
-       /**
-        * call this instead of tableName() in the updater when renaming tables
-        * @param string $name
-        * @param string $format One of quoted, raw, or split
-        * @return string
-        */
-       function realTableName( $name, $format = 'quoted' ) {
-               $table = parent::tableName( $name, $format );
-               if ( $format == 'split' ) {
-                       // Used internally, we want the schema split off from the table name and returned
-                       // as a list with 3 elements (database, schema, table)
-                       $table = explode( '.', $table );
-                       while ( count( $table ) < 3 ) {
-                               array_unshift( $table, false );
-                       }
-               }
-               return $table;
-       }
-
-       /**
-        * Delete a table
-        * @param string $tableName
-        * @param string $fName
-        * @return bool|ResultWrapper
-        * @since 1.18
-        */
-       public function dropTable( $tableName, $fName = __METHOD__ ) {
-               if ( !$this->tableExists( $tableName, $fName ) ) {
-                       return false;
-               }
-
-               // parent function incorrectly appends CASCADE, which we don't want
-               $sql = "DROP TABLE " . $this->tableName( $tableName );
-
-               return $this->query( $sql, $fName );
-       }
-
-       /**
-        * Called in the installer and updater.
-        * Probably doesn't need to be called anywhere else in the codebase.
-        * @param bool|null $value
-        * @return bool|null
-        */
-       public function prepareStatements( $value = null ) {
-               return wfSetVar( $this->mPrepareStatements, $value );
-       }
-
-       /**
-        * Called in the installer and updater.
-        * Probably doesn't need to be called anywhere else in the codebase.
-        * @param bool|null $value
-        * @return bool|null
-        */
-       public function scrollableCursor( $value = null ) {
-               return wfSetVar( $this->mScrollableCursor, $value );
-       }
-
-       /**
-        * Called in the installer and updater.
-        * Probably doesn't need to be called anywhere else in the codebase.
-        * @param array|null $value
-        * @return array|null
-        */
-       public function ignoreErrors( array $value = null ) {
-               return wfSetVar( $this->mIgnoreErrors, $value );
-       }
-} // end DatabaseMssql class
index d8ed7a9..4aab811 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @ingroup Database
  */
+use Wikimedia\Rdbms\Blob;
 
 /**
  * The oci8 extension is fairly weak and doesn't support oci_num_rows, among
@@ -1085,24 +1086,18 @@ class DatabaseOracle extends Database {
                }
        }
 
-       /**
-        * defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
-        *
-        * @param resource $fp
-        * @param bool|string $lineCallback
-        * @param bool|callable $resultCallback
-        * @param string $fname
-        * @param bool|callable $inputCallback
-        * @return bool|string
-        */
-       function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
-               $fname = __METHOD__, $inputCallback = false ) {
+       function sourceStream(
+               $fp,
+               callable $lineCallback = null,
+               callable $resultCallback = null,
+               $fname = __METHOD__, callable $inputCallback = null
+       ) {
                $cmd = '';
                $done = false;
                $dollarquote = false;
 
                $replacements = [];
-
+               // Defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
                while ( !feof( $fp ) ) {
                        if ( $lineCallback ) {
                                call_user_func( $lineCallback );
index 09d8fab..0186222 100644 (file)
@@ -72,7 +72,13 @@ abstract class MWLBFactory {
                                                        // Work around the reserved word usage in MediaWiki schema
                                                        'keywordTableMap' => [ 'user' => 'mwuser', 'text' => 'pagecontent' ]
                                                ];
+                                       } elseif ( $server['type'] === 'mssql' ) {
+                                               $server += [
+                                                       'port' => $mainConfig->get( 'DBport' ),
+                                                       'useWindowsAuth' => $mainConfig->get( 'DBWindowsAuthentication' )
+                                               ];
                                        }
+
                                        if ( in_array( $server['type'], $typesWithSchema, true ) ) {
                                                $server += [ 'schema' => $mainConfig->get( 'DBmwschema' ) ];
                                        }
@@ -112,6 +118,9 @@ abstract class MWLBFactory {
                                        $server['port'] = $mainConfig->get( 'DBport' );
                                        // Work around the reserved word usage in MediaWiki schema
                                        $server['keywordTableMap'] = [ 'user' => 'mwuser', 'text' => 'pagecontent' ];
+                               } elseif ( $server['type'] === 'mssql' ) {
+                                       $server['port'] = $mainConfig->get( 'DBport' );
+                                       $server['useWindowsAuth'] = $mainConfig->get( 'DBWindowsAuthentication' );
                                }
                                $lbConf['servers'] = [ $server ];
                        }
index 2e06c40..62e635d 100644 (file)
@@ -22,6 +22,8 @@
  * @author Aaron Schulz
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Version of FileJournal that logs to a DB table
  * @since 1.20
@@ -180,7 +182,7 @@ class DBFileJournal extends FileJournal {
        protected function getMasterDB() {
                if ( !$this->dbw ) {
                        // Get a separate connection in autocommit mode
-                       $lb = wfGetLBFactory()->newMainLB();
+                       $lb =  MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->newMainLB();
                        $this->dbw = $lb->getConnection( DB_MASTER, [], $this->wiki );
                        $this->dbw->clearFlag( DBO_TRX );
                }
index c6c49b4..f6f44e6 100644 (file)
@@ -120,10 +120,10 @@ class ForeignDBFile extends LocalFile {
        }
 
        /**
-        * @param bool|Language $lang Optional language to fetch description in.
+        * @param Language|null $lang Optional language to fetch description in.
         * @return string|false
         */
-       function getDescriptionText( $lang = false ) {
+       function getDescriptionText( $lang = null ) {
                global $wgLang;
 
                if ( !$this->repo->fetchDescription ) {
index 9be6c3d..7fa5a3d 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  * @ingroup Deployment
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * Mysql update list and mysql-specific update functions.
@@ -853,7 +854,8 @@ class MysqlUpdater extends DatabaseUpdater {
                        foreach ( $res as $row ) {
                                $count = ( $count + 1 ) % 100;
                                if ( $count == 0 ) {
-                                       wfGetLBFactory()->waitForReplication( [ 'wiki' => wfWikiID() ] );
+                                       $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                                       $lbFactory->waitForReplication( [ 'wiki' => wfWikiID() ] );
                                }
                                $this->db->insert( 'templatelinks',
                                        [
index 4a1ed65..5c2b320 100644 (file)
        "config-install-subscribe-fail": "Невъзможно беше абонирането за mediawiki-announce: $1",
        "config-install-subscribe-notpossible": "не е инсталиран cURL и <code>allow_url_fopen</code> не е налична.",
        "config-install-mainpage": "Създаване на Началната страница със съдържание по подразбиране",
+       "config-install-mainpage-exists": "Главната страница вече съществува, преминаване напред",
        "config-install-extension-tables": "Създаване на таблици за включените разширения",
        "config-install-mainpage-failed": "Вмъкването на Началната страница беше невъзможно: $1",
        "config-install-done": "<strong>Поздравления!</strong>\nИнсталирането на МедияУики приключи успешно.\n\nИнсталаторът създаде файл <code>LocalSettings.php</code>.\nТой съдържа всичката необходима основна конфигурация на уикито.\n\nНеобходимо е той да бъде изтеглен и поставен в основната директория на уикито (директорията, в която е и index.php). Изтеглянето би трябвало да започне автоматично.\n\nАко изтеглянето не започне автоматично или е било прекратено, файлът може да бъде изтеглен чрез щракване на препратката по-долу:\n\n$3\n\n<strong>Забележка:</strong> Ако това не бъде извършено сега, генерираният конфигурационен файл няма да е достъпен на по-късен етап ако не бъде изтеглен сега или инсталацията приключи без изтеглянето му.\n\nКогато файлът вече е в основната директория, <strong>[$2 уикито ще е достъпно на този адрес]</strong>.",
index f029d0a..f292ab3 100644 (file)
        "config-nofile": "\"$1\" 파일을 찾을 수 없습니다. 이미 삭제되었나요?",
        "config-extension-link": "당신의 위키가 [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions 확장 기능]을 지원한다는 것을 알고 계십니까?\n\n[https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category 분류별 확장 기능]을 찾아보실 수 있습니다.",
        "mainpagetext": "<strong>미디어위키가 설치되었습니다.</strong>",
-       "mainpagedocfooter": "[https://meta.wikimedia.org/wiki/Help:Contents 이곳]에서 위키 소프트웨어에 대한 정보를 얻을 수 있습니다.\n\n== 시작하기 ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings 설정하기 목록]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ 미디어위키 FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 미디어위키 릴리스 메일링 리스트]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources 내 언어로 미디어위키 지역화]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam 당신의 위키에서 스팸에 대처하는 법을 배우세요]"
+       "mainpagedocfooter": "[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 이곳]에서 위키 소프트웨어에 대한 정보를 얻을 수 있습니다.\n\n== 시작하기 ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings 설정하기 목록]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ 미디어위키 FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 미디어위키 릴리스 메일링 리스트]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources 내 언어로 미디어위키 지역화]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam 당신의 위키에서 스팸에 대처하는 법을 배우세요]"
 }
index f09ba57..2d816f9 100644 (file)
@@ -22,6 +22,8 @@
  * @ingroup Cache
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Job to purge the cache for all pages that link to or use another page or file
  *
@@ -113,7 +115,7 @@ class HTMLCacheUpdateJob extends Job {
                $touchTimestamp = wfTimestampNow();
 
                $dbw = wfGetDB( DB_MASTER );
-               $factory = wfGetLBFactory();
+               $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
                // Update page_touched (skipping pages already touched since the root job).
                // Check $wgUpdateRowsPerQuery for sanity; batch jobs are sized by that already.
index 0daa4ed..99e509c 100644 (file)
@@ -28,7 +28,6 @@ use Psr\Log\LoggerInterface;
 use Psr\Log\NullLogger;
 use Wikimedia\WaitConditionLoop;
 use BagOStuff;
-use DBMasterPos;
 
 /**
  * Class for ensuring a consistent ordering of events as seen by the user, despite replication.
index a8f664d..fc3ebe0 100644 (file)
@@ -2,6 +2,7 @@
 
 use Wikimedia\Rdbms\DatabaseDomain;
 use Wikimedia\Rdbms\ILoadBalancer;
+use Wikimedia\Rdbms\DBMasterPos;
 
 /**
  * Helper class to handle automatically marking connections as reusable (via RAII pattern)
index 72e39b7..9d800a2 100644 (file)
@@ -29,6 +29,8 @@ use Wikimedia\ScopedCallback;
 use Wikimedia\Rdbms\TransactionProfiler;
 use Wikimedia\Rdbms\LikeMatch;
 use Wikimedia\Rdbms\DatabaseDomain;
+use Wikimedia\Rdbms\DBMasterPos;
+use Wikimedia\Rdbms\Blob;
 
 /**
  * Relational database abstraction object
diff --git a/includes/libs/rdbms/database/DatabaseMssql.php b/includes/libs/rdbms/database/DatabaseMssql.php
new file mode 100644 (file)
index 0000000..c1dfdb4
--- /dev/null
@@ -0,0 +1,1359 @@
+<?php
+/**
+ * This is the MS SQL Server Native database abstraction layer.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ * @author Joel Penner <a-joelpe at microsoft dot com>
+ * @author Chris Pucci <a-cpucci at microsoft dot com>
+ * @author Ryan Biesemeyer <v-ryanbi at microsoft dot com>
+ * @author Ryan Schmidt <skizzerz at gmail dot com>
+ */
+use Wikimedia\Rdbms\Blob;
+use Wikimedia\Rdbms\MssqlBlob;
+
+/**
+ * @ingroup Database
+ */
+class DatabaseMssql extends Database {
+       protected $mPort;
+       protected $mUseWindowsAuth = false;
+
+       protected $mInsertId = null;
+       protected $mLastResult = null;
+       protected $mAffectedRows = null;
+       protected $mSubqueryId = 0;
+       protected $mScrollableCursor = true;
+       protected $mPrepareStatements = true;
+       protected $mBinaryColumnCache = null;
+       protected $mBitColumnCache = null;
+       protected $mIgnoreDupKeyErrors = false;
+       protected $mIgnoreErrors = [];
+
+       public function implicitGroupby() {
+               return false;
+       }
+
+       public function implicitOrderby() {
+               return false;
+       }
+
+       public function unionSupportsOrderAndLimit() {
+               return false;
+       }
+
+       public function __construct( array $params ) {
+               $this->mPort = $params['port'];
+               $this->mUseWindowsAuth = $params['UseWindowsAuth'];
+
+               parent::__construct( $params );
+       }
+
+       /**
+        * Usually aborts on failure
+        * @param string $server
+        * @param string $user
+        * @param string $password
+        * @param string $dbName
+        * @throws DBConnectionError
+        * @return bool|resource|null
+        */
+       public function open( $server, $user, $password, $dbName ) {
+               # Test for driver support, to avoid suppressed fatal error
+               if ( !function_exists( 'sqlsrv_connect' ) ) {
+                       throw new DBConnectionError(
+                               $this,
+                               "Microsoft SQL Server Native (sqlsrv) functions missing.
+                               You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n"
+                       );
+               }
+
+               # e.g. the class is being loaded
+               if ( !strlen( $user ) ) {
+                       return null;
+               }
+
+               $this->close();
+               $this->mServer = $server;
+               $this->mUser = $user;
+               $this->mPassword = $password;
+               $this->mDBname = $dbName;
+
+               $connectionInfo = [];
+
+               if ( $dbName ) {
+                       $connectionInfo['Database'] = $dbName;
+               }
+
+               // Decide which auth scenerio to use
+               // if we are using Windows auth, then don't add credentials to $connectionInfo
+               if ( !$this->mUseWindowsAuth ) {
+                       $connectionInfo['UID'] = $user;
+                       $connectionInfo['PWD'] = $password;
+               }
+
+               MediaWiki\suppressWarnings();
+               $this->mConn = sqlsrv_connect( $server, $connectionInfo );
+               MediaWiki\restoreWarnings();
+
+               if ( $this->mConn === false ) {
+                       throw new DBConnectionError( $this, $this->lastError() );
+               }
+
+               $this->mOpened = true;
+
+               return $this->mConn;
+       }
+
+       /**
+        * Closes a database connection, if it is open
+        * Returns success, true if already closed
+        * @return bool
+        */
+       protected function closeConnection() {
+               return sqlsrv_close( $this->mConn );
+       }
+
+       /**
+        * @param bool|MssqlResultWrapper|resource $result
+        * @return bool|MssqlResultWrapper
+        */
+       protected function resultObject( $result ) {
+               if ( !$result ) {
+                       return false;
+               } elseif ( $result instanceof MssqlResultWrapper ) {
+                       return $result;
+               } elseif ( $result === true ) {
+                       // Successful write query
+                       return $result;
+               } else {
+                       return new MssqlResultWrapper( $this, $result );
+               }
+       }
+
+       /**
+        * @param string $sql
+        * @return bool|MssqlResultWrapper|resource
+        * @throws DBUnexpectedError
+        */
+       protected function doQuery( $sql ) {
+               // several extensions seem to think that all databases support limits
+               // via LIMIT N after the WHERE clause, but  MSSQL uses SELECT TOP N,
+               // so to catch any of those extensions we'll do a quick check for a
+               // LIMIT clause and pass $sql through $this->LimitToTopN() which parses
+               // the LIMIT clause and passes the result to $this->limitResult();
+               if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
+                       // massage LIMIT -> TopN
+                       $sql = $this->LimitToTopN( $sql );
+               }
+
+               // MSSQL doesn't have EXTRACT(epoch FROM XXX)
+               if ( preg_match( '#\bEXTRACT\s*?\(\s*?EPOCH\s+FROM\b#i', $sql, $matches ) ) {
+                       // This is same as UNIX_TIMESTAMP, we need to calc # of seconds from 1970
+                       $sql = str_replace( $matches[0], "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),", $sql );
+               }
+
+               // perform query
+
+               // SQLSRV_CURSOR_STATIC is slower than SQLSRV_CURSOR_CLIENT_BUFFERED (one of the two is
+               // needed if we want to be able to seek around the result set), however CLIENT_BUFFERED
+               // has a bug in the sqlsrv driver where wchar_t types (such as nvarchar) that are empty
+               // strings make php throw a fatal error "Severe error translating Unicode"
+               if ( $this->mScrollableCursor ) {
+                       $scrollArr = [ 'Scrollable' => SQLSRV_CURSOR_STATIC ];
+               } else {
+                       $scrollArr = [];
+               }
+
+               if ( $this->mPrepareStatements ) {
+                       // we do prepare + execute so we can get its field metadata for later usage if desired
+                       $stmt = sqlsrv_prepare( $this->mConn, $sql, [], $scrollArr );
+                       $success = sqlsrv_execute( $stmt );
+               } else {
+                       $stmt = sqlsrv_query( $this->mConn, $sql, [], $scrollArr );
+                       $success = (bool)$stmt;
+               }
+
+               // Make a copy to ensure what we add below does not get reflected in future queries
+               $ignoreErrors = $this->mIgnoreErrors;
+
+               if ( $this->mIgnoreDupKeyErrors ) {
+                       // ignore duplicate key errors
+                       // this emulates INSERT IGNORE in MySQL
+                       $ignoreErrors[] = '2601'; // duplicate key error caused by unique index
+                       $ignoreErrors[] = '2627'; // duplicate key error caused by primary key
+                       $ignoreErrors[] = '3621'; // generic "the statement has been terminated" error
+               }
+
+               if ( $success === false ) {
+                       $errors = sqlsrv_errors();
+                       $success = true;
+
+                       foreach ( $errors as $err ) {
+                               if ( !in_array( $err['code'], $ignoreErrors ) ) {
+                                       $success = false;
+                                       break;
+                               }
+                       }
+
+                       if ( $success === false ) {
+                               return false;
+                       }
+               }
+               // remember number of rows affected
+               $this->mAffectedRows = sqlsrv_rows_affected( $stmt );
+
+               return $stmt;
+       }
+
+       public function freeResult( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               sqlsrv_free_stmt( $res );
+       }
+
+       /**
+        * @param MssqlResultWrapper $res
+        * @return stdClass
+        */
+       public function fetchObject( $res ) {
+               // $res is expected to be an instance of MssqlResultWrapper here
+               return $res->fetchObject();
+       }
+
+       /**
+        * @param MssqlResultWrapper $res
+        * @return array
+        */
+       public function fetchRow( $res ) {
+               return $res->fetchRow();
+       }
+
+       /**
+        * @param mixed $res
+        * @return int
+        */
+       public function numRows( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               $ret = sqlsrv_num_rows( $res );
+
+               if ( $ret === false ) {
+                       // we cannot get an amount of rows from this cursor type
+                       // has_rows returns bool true/false if the result has rows
+                       $ret = (int)sqlsrv_has_rows( $res );
+               }
+
+               return $ret;
+       }
+
+       /**
+        * @param mixed $res
+        * @return int
+        */
+       public function numFields( $res ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return sqlsrv_num_fields( $res );
+       }
+
+       /**
+        * @param mixed $res
+        * @param int $n
+        * @return int
+        */
+       public function fieldName( $res, $n ) {
+               if ( $res instanceof ResultWrapper ) {
+                       $res = $res->result;
+               }
+
+               return sqlsrv_field_metadata( $res )[$n]['Name'];
+       }
+
+       /**
+        * This must be called after nextSequenceVal
+        * @return int|null
+        */
+       public function insertId() {
+               return $this->mInsertId;
+       }
+
+       /**
+        * @param MssqlResultWrapper $res
+        * @param int $row
+        * @return bool
+        */
+       public function dataSeek( $res, $row ) {
+               return $res->seek( $row );
+       }
+
+       /**
+        * @return string
+        */
+       public function lastError() {
+               $strRet = '';
+               $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
+               if ( $retErrors != null ) {
+                       foreach ( $retErrors as $arrError ) {
+                               $strRet .= $this->formatError( $arrError ) . "\n";
+                       }
+               } else {
+                       $strRet = "No errors found";
+               }
+
+               return $strRet;
+       }
+
+       /**
+        * @param array $err
+        * @return string
+        */
+       private function formatError( $err ) {
+               return '[SQLSTATE ' .
+                       $err['SQLSTATE'] . '][Error Code ' . $err['code'] . ']' . $err['message'];
+       }
+
+       /**
+        * @return string|int
+        */
+       public function lastErrno() {
+               $err = sqlsrv_errors( SQLSRV_ERR_ALL );
+               if ( $err !== null && isset( $err[0] ) ) {
+                       return $err[0]['code'];
+               } else {
+                       return 0;
+               }
+       }
+
+       /**
+        * @return int
+        */
+       public function affectedRows() {
+               return $this->mAffectedRows;
+       }
+
+       /**
+        * SELECT wrapper
+        *
+        * @param mixed $table Array or string, table name(s) (prefix auto-added)
+        * @param mixed $vars Array or string, field name(s) to be retrieved
+        * @param mixed $conds Array or string, condition(s) for WHERE
+        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+        * @param array $options Associative array of options (e.g.
+        *   [ 'GROUP BY' => 'page_title' ]), see Database::makeSelectOptions
+        *   code for list of supported stuff
+        * @param array $join_conds Associative array of table join conditions
+        *   (optional) (e.g. [ 'page' => [ 'LEFT JOIN','page_latest=rev_id' ] ]
+        * @return mixed Database result resource (feed to Database::fetchObject
+        *   or whatever), or false on failure
+        * @throws DBQueryError
+        * @throws DBUnexpectedError
+        * @throws Exception
+        */
+       public function select( $table, $vars, $conds = '', $fname = __METHOD__,
+               $options = [], $join_conds = []
+       ) {
+               $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+               if ( isset( $options['EXPLAIN'] ) ) {
+                       try {
+                               $this->mScrollableCursor = false;
+                               $this->mPrepareStatements = false;
+                               $this->query( "SET SHOWPLAN_ALL ON" );
+                               $ret = $this->query( $sql, $fname );
+                               $this->query( "SET SHOWPLAN_ALL OFF" );
+                       } catch ( DBQueryError $dqe ) {
+                               if ( isset( $options['FOR COUNT'] ) ) {
+                                       // likely don't have privs for SHOWPLAN, so run a select count instead
+                                       $this->query( "SET SHOWPLAN_ALL OFF" );
+                                       unset( $options['EXPLAIN'] );
+                                       $ret = $this->select(
+                                               $table,
+                                               'COUNT(*) AS EstimateRows',
+                                               $conds,
+                                               $fname,
+                                               $options,
+                                               $join_conds
+                                       );
+                               } else {
+                                       // someone actually wanted the query plan instead of an est row count
+                                       // let them know of the error
+                                       $this->mScrollableCursor = true;
+                                       $this->mPrepareStatements = true;
+                                       throw $dqe;
+                               }
+                       }
+                       $this->mScrollableCursor = true;
+                       $this->mPrepareStatements = true;
+                       return $ret;
+               }
+               return $this->query( $sql, $fname );
+       }
+
+       /**
+        * SELECT wrapper
+        *
+        * @param mixed $table Array or string, table name(s) (prefix auto-added)
+        * @param mixed $vars Array or string, field name(s) to be retrieved
+        * @param mixed $conds Array or string, condition(s) for WHERE
+        * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+        * @param array $options Associative array of options (e.g. [ 'GROUP BY' => 'page_title' ]),
+        *   see Database::makeSelectOptions code for list of supported stuff
+        * @param array $join_conds Associative array of table join conditions (optional)
+        *    (e.g. [ 'page' => [ 'LEFT JOIN','page_latest=rev_id' ] ]
+        * @return string The SQL text
+        */
+       public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
+               $options = [], $join_conds = []
+       ) {
+               if ( isset( $options['EXPLAIN'] ) ) {
+                       unset( $options['EXPLAIN'] );
+               }
+
+               $sql = parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+
+               // try to rewrite aggregations of bit columns (currently MAX and MIN)
+               if ( strpos( $sql, 'MAX(' ) !== false || strpos( $sql, 'MIN(' ) !== false ) {
+                       $bitColumns = [];
+                       if ( is_array( $table ) ) {
+                               foreach ( $table as $t ) {
+                                       $bitColumns += $this->getBitColumns( $this->tableName( $t ) );
+                               }
+                       } else {
+                               $bitColumns = $this->getBitColumns( $this->tableName( $table ) );
+                       }
+
+                       foreach ( $bitColumns as $col => $info ) {
+                               $replace = [
+                                       "MAX({$col})" => "MAX(CAST({$col} AS tinyint))",
+                                       "MIN({$col})" => "MIN(CAST({$col} AS tinyint))",
+                               ];
+                               $sql = str_replace( array_keys( $replace ), array_values( $replace ), $sql );
+                       }
+               }
+
+               return $sql;
+       }
+
+       public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
+               $fname = __METHOD__
+       ) {
+               $this->mScrollableCursor = false;
+               try {
+                       parent::deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname );
+               } catch ( Exception $e ) {
+                       $this->mScrollableCursor = true;
+                       throw $e;
+               }
+               $this->mScrollableCursor = true;
+       }
+
+       public function delete( $table, $conds, $fname = __METHOD__ ) {
+               $this->mScrollableCursor = false;
+               try {
+                       parent::delete( $table, $conds, $fname );
+               } catch ( Exception $e ) {
+                       $this->mScrollableCursor = true;
+                       throw $e;
+               }
+               $this->mScrollableCursor = true;
+       }
+
+       /**
+        * Estimate rows in dataset
+        * Returns estimated count, based on SHOWPLAN_ALL output
+        * This is not necessarily an accurate estimate, so use sparingly
+        * Returns -1 if count cannot be found
+        * Takes same arguments as Database::select()
+        * @param string $table
+        * @param string $vars
+        * @param string $conds
+        * @param string $fname
+        * @param array $options
+        * @return int
+        */
+       public function estimateRowCount( $table, $vars = '*', $conds = '',
+               $fname = __METHOD__, $options = []
+       ) {
+               // http://msdn2.microsoft.com/en-us/library/aa259203.aspx
+               $options['EXPLAIN'] = true;
+               $options['FOR COUNT'] = true;
+               $res = $this->select( $table, $vars, $conds, $fname, $options );
+
+               $rows = -1;
+               if ( $res ) {
+                       $row = $this->fetchRow( $res );
+
+                       if ( isset( $row['EstimateRows'] ) ) {
+                               $rows = (int)$row['EstimateRows'];
+                       }
+               }
+
+               return $rows;
+       }
+
+       /**
+        * Returns information about an index
+        * If errors are explicitly ignored, returns NULL on failure
+        * @param string $table
+        * @param string $index
+        * @param string $fname
+        * @return array|bool|null
+        */
+       public function indexInfo( $table, $index, $fname = __METHOD__ ) {
+               # This does not return the same info as MYSQL would, but that's OK
+               # because MediaWiki never uses the returned value except to check for
+               # the existence of indexes.
+               $sql = "sp_helpindex '" . $this->tableName( $table ) . "'";
+               $res = $this->query( $sql, $fname );
+
+               if ( !$res ) {
+                       return null;
+               }
+
+               $result = [];
+               foreach ( $res as $row ) {
+                       if ( $row->index_name == $index ) {
+                               $row->Non_unique = !stristr( $row->index_description, "unique" );
+                               $cols = explode( ", ", $row->index_keys );
+                               foreach ( $cols as $col ) {
+                                       $row->Column_name = trim( $col );
+                                       $result[] = clone $row;
+                               }
+                       } elseif ( $index == 'PRIMARY' && stristr( $row->index_description, 'PRIMARY' ) ) {
+                               $row->Non_unique = 0;
+                               $cols = explode( ", ", $row->index_keys );
+                               foreach ( $cols as $col ) {
+                                       $row->Column_name = trim( $col );
+                                       $result[] = clone $row;
+                               }
+                       }
+               }
+
+               return empty( $result ) ? false : $result;
+       }
+
+       /**
+        * INSERT wrapper, inserts an array into a table
+        *
+        * $arrToInsert may be a single associative array, or an array of these with numeric keys, for
+        * multi-row insert.
+        *
+        * Usually aborts on failure
+        * If errors are explicitly ignored, returns success
+        * @param string $table
+        * @param array $arrToInsert
+        * @param string $fname
+        * @param array $options
+        * @return bool
+        * @throws Exception
+        */
+       public function insert( $table, $arrToInsert, $fname = __METHOD__, $options = [] ) {
+               # No rows to insert, easy just return now
+               if ( !count( $arrToInsert ) ) {
+                       return true;
+               }
+
+               if ( !is_array( $options ) ) {
+                       $options = [ $options ];
+               }
+
+               $table = $this->tableName( $table );
+
+               if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) { // Not multi row
+                       $arrToInsert = [ 0 => $arrToInsert ]; // make everything multi row compatible
+               }
+
+               // We know the table we're inserting into, get its identity column
+               $identity = null;
+               // strip matching square brackets and the db/schema from table name
+               $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
+               $tableRaw = array_pop( $tableRawArr );
+               $res = $this->doQuery(
+                       "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS " .
+                               "WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'"
+               );
+               if ( $res && sqlsrv_has_rows( $res ) ) {
+                       // There is an identity for this table.
+                       $identityArr = sqlsrv_fetch_array( $res, SQLSRV_FETCH_ASSOC );
+                       $identity = array_pop( $identityArr );
+               }
+               sqlsrv_free_stmt( $res );
+
+               // Determine binary/varbinary fields so we can encode data as a hex string like 0xABCDEF
+               $binaryColumns = $this->getBinaryColumns( $table );
+
+               // INSERT IGNORE is not supported by SQL Server
+               // remove IGNORE from options list and set ignore flag to true
+               if ( in_array( 'IGNORE', $options ) ) {
+                       $options = array_diff( $options, [ 'IGNORE' ] );
+                       $this->mIgnoreDupKeyErrors = true;
+               }
+
+               $ret = null;
+               foreach ( $arrToInsert as $a ) {
+                       // start out with empty identity column, this is so we can return
+                       // it as a result of the INSERT logic
+                       $sqlPre = '';
+                       $sqlPost = '';
+                       $identityClause = '';
+
+                       // if we have an identity column
+                       if ( $identity ) {
+                               // iterate through
+                               foreach ( $a as $k => $v ) {
+                                       if ( $k == $identity ) {
+                                               if ( !is_null( $v ) ) {
+                                                       // there is a value being passed to us,
+                                                       // we need to turn on and off inserted identity
+                                                       $sqlPre = "SET IDENTITY_INSERT $table ON;";
+                                                       $sqlPost = ";SET IDENTITY_INSERT $table OFF;";
+                                               } else {
+                                                       // we can't insert NULL into an identity column,
+                                                       // so remove the column from the insert.
+                                                       unset( $a[$k] );
+                                               }
+                                       }
+                               }
+
+                               // we want to output an identity column as result
+                               $identityClause = "OUTPUT INSERTED.$identity ";
+                       }
+
+                       $keys = array_keys( $a );
+
+                       // Build the actual query
+                       $sql = $sqlPre . 'INSERT ' . implode( ' ', $options ) .
+                               " INTO $table (" . implode( ',', $keys ) . ") $identityClause VALUES (";
+
+                       $first = true;
+                       foreach ( $a as $key => $value ) {
+                               if ( isset( $binaryColumns[$key] ) ) {
+                                       $value = new MssqlBlob( $value );
+                               }
+                               if ( $first ) {
+                                       $first = false;
+                               } else {
+                                       $sql .= ',';
+                               }
+                               if ( is_null( $value ) ) {
+                                       $sql .= 'null';
+                               } elseif ( is_array( $value ) || is_object( $value ) ) {
+                                       if ( is_object( $value ) && $value instanceof Blob ) {
+                                               $sql .= $this->addQuotes( $value );
+                                       } else {
+                                               $sql .= $this->addQuotes( serialize( $value ) );
+                                       }
+                               } else {
+                                       $sql .= $this->addQuotes( $value );
+                               }
+                       }
+                       $sql .= ')' . $sqlPost;
+
+                       // Run the query
+                       $this->mScrollableCursor = false;
+                       try {
+                               $ret = $this->query( $sql );
+                       } catch ( Exception $e ) {
+                               $this->mScrollableCursor = true;
+                               $this->mIgnoreDupKeyErrors = false;
+                               throw $e;
+                       }
+                       $this->mScrollableCursor = true;
+
+                       if ( $ret instanceof ResultWrapper && !is_null( $identity ) ) {
+                               // Then we want to get the identity column value we were assigned and save it off
+                               $row = $ret->fetchObject();
+                               if ( is_object( $row ) ) {
+                                       $this->mInsertId = $row->$identity;
+                                       // It seems that mAffectedRows is -1 sometimes when OUTPUT INSERTED.identity is
+                                       // used if we got an identity back, we know for sure a row was affected, so
+                                       // adjust that here
+                                       if ( $this->mAffectedRows == -1 ) {
+                                               $this->mAffectedRows = 1;
+                                       }
+                               }
+                       }
+               }
+
+               $this->mIgnoreDupKeyErrors = false;
+
+               return $ret;
+       }
+
+       /**
+        * INSERT SELECT wrapper
+        * $varMap must be an associative array of the form [ 'dest1' => 'source1', ... ]
+        * Source items may be literals rather than field names, but strings should
+        * be quoted with Database::addQuotes().
+        * @param string $destTable
+        * @param array|string $srcTable May be an array of tables.
+        * @param array $varMap
+        * @param array $conds May be "*" to copy the whole table.
+        * @param string $fname
+        * @param array $insertOptions
+        * @param array $selectOptions
+        * @return null|ResultWrapper
+        * @throws Exception
+        */
+       public function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
+               $insertOptions = [], $selectOptions = []
+       ) {
+               $this->mScrollableCursor = false;
+               try {
+                       $ret = parent::nativeInsertSelect(
+                               $destTable,
+                               $srcTable,
+                               $varMap,
+                               $conds,
+                               $fname,
+                               $insertOptions,
+                               $selectOptions
+                       );
+               } catch ( Exception $e ) {
+                       $this->mScrollableCursor = true;
+                       throw $e;
+               }
+               $this->mScrollableCursor = true;
+
+               return $ret;
+       }
+
+       /**
+        * UPDATE wrapper. Takes a condition array and a SET array.
+        *
+        * @param string $table Name of the table to UPDATE. This will be passed through
+        *                Database::tableName().
+        *
+        * @param array $values An array of values to SET. For each array element,
+        *                the key gives the field name, and the value gives the data
+        *                to set that field to. The data will be quoted by
+        *                Database::addQuotes().
+        *
+        * @param array $conds An array of conditions (WHERE). See
+        *                Database::select() for the details of the format of
+        *                condition arrays. Use '*' to update all rows.
+        *
+        * @param string $fname The function name of the caller (from __METHOD__),
+        *                for logging and profiling.
+        *
+        * @param array $options An array of UPDATE options, can be:
+        *                   - IGNORE: Ignore unique key conflicts
+        *                   - LOW_PRIORITY: MySQL-specific, see MySQL manual.
+        * @return bool
+        * @throws DBUnexpectedError
+        * @throws Exception
+        */
+       function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
+               $table = $this->tableName( $table );
+               $binaryColumns = $this->getBinaryColumns( $table );
+
+               $opts = $this->makeUpdateOptions( $options );
+               $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET, $binaryColumns );
+
+               if ( $conds !== [] && $conds !== '*' ) {
+                       $sql .= " WHERE " . $this->makeList( $conds, LIST_AND, $binaryColumns );
+               }
+
+               $this->mScrollableCursor = false;
+               try {
+                       $this->query( $sql );
+               } catch ( Exception $e ) {
+                       $this->mScrollableCursor = true;
+                       throw $e;
+               }
+               $this->mScrollableCursor = true;
+               return true;
+       }
+
+       /**
+        * Makes an encoded list of strings from an array
+        * @param array $a Containing the data
+        * @param int $mode Constant
+        *      - LIST_COMMA:          comma separated, no field names
+        *      - LIST_AND:            ANDed WHERE clause (without the WHERE). See
+        *        the documentation for $conds in Database::select().
+        *      - LIST_OR:             ORed WHERE clause (without the WHERE)
+        *      - LIST_SET:            comma separated with field names, like a SET clause
+        *      - LIST_NAMES:          comma separated field names
+        * @param array $binaryColumns Contains a list of column names that are binary types
+        *      This is a custom parameter only present for MS SQL.
+        *
+        * @throws DBUnexpectedError
+        * @return string
+        */
+       public function makeList( $a, $mode = LIST_COMMA, $binaryColumns = [] ) {
+               if ( !is_array( $a ) ) {
+                       throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
+               }
+
+               if ( $mode != LIST_NAMES ) {
+                       // In MS SQL, values need to be specially encoded when they are
+                       // inserted into binary fields. Perform this necessary encoding
+                       // for the specified set of columns.
+                       foreach ( array_keys( $a ) as $field ) {
+                               if ( !isset( $binaryColumns[$field] ) ) {
+                                       continue;
+                               }
+
+                               if ( is_array( $a[$field] ) ) {
+                                       foreach ( $a[$field] as &$v ) {
+                                               $v = new MssqlBlob( $v );
+                                       }
+                                       unset( $v );
+                               } else {
+                                       $a[$field] = new MssqlBlob( $a[$field] );
+                               }
+                       }
+               }
+
+               return parent::makeList( $a, $mode );
+       }
+
+       /**
+        * @param string $table
+        * @param string $field
+        * @return int Returns the size of a text field, or -1 for "unlimited"
+        */
+       public function textFieldSize( $table, $field ) {
+               $table = $this->tableName( $table );
+               $sql = "SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
+                       WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
+               $res = $this->query( $sql );
+               $row = $this->fetchRow( $res );
+               $size = -1;
+               if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) {
+                       $size = $row['CHARACTER_MAXIMUM_LENGTH'];
+               }
+
+               return $size;
+       }
+
+       /**
+        * Construct a LIMIT query with optional offset
+        * This is used for query pages
+        *
+        * @param string $sql SQL query we will append the limit too
+        * @param int $limit The SQL limit
+        * @param bool|int $offset The SQL offset (default false)
+        * @return array|string
+        * @throws DBUnexpectedError
+        */
+       public function limitResult( $sql, $limit, $offset = false ) {
+               if ( $offset === false || $offset == 0 ) {
+                       if ( strpos( $sql, "SELECT" ) === false ) {
+                               return "TOP {$limit} " . $sql;
+                       } else {
+                               return preg_replace( '/\bSELECT(\s+DISTINCT)?\b/Dsi',
+                                       'SELECT$1 TOP ' . $limit, $sql, 1 );
+                       }
+               } else {
+                       // This one is fun, we need to pull out the select list as well as any ORDER BY clause
+                       $select = $orderby = [];
+                       $s1 = preg_match( '#SELECT\s+(.+?)\s+FROM#Dis', $sql, $select );
+                       $s2 = preg_match( '#(ORDER BY\s+.+?)(\s*FOR XML .*)?$#Dis', $sql, $orderby );
+                       $postOrder = '';
+                       $first = $offset + 1;
+                       $last = $offset + $limit;
+                       $sub1 = 'sub_' . $this->mSubqueryId;
+                       $sub2 = 'sub_' . ( $this->mSubqueryId + 1 );
+                       $this->mSubqueryId += 2;
+                       if ( !$s1 ) {
+                               // wat
+                               throw new DBUnexpectedError( $this, "Attempting to LIMIT a non-SELECT query\n" );
+                       }
+                       if ( !$s2 ) {
+                               // no ORDER BY
+                               $overOrder = 'ORDER BY (SELECT 1)';
+                       } else {
+                               if ( !isset( $orderby[2] ) || !$orderby[2] ) {
+                                       // don't need to strip it out if we're using a FOR XML clause
+                                       $sql = str_replace( $orderby[1], '', $sql );
+                               }
+                               $overOrder = $orderby[1];
+                               $postOrder = ' ' . $overOrder;
+                       }
+                       $sql = "SELECT {$select[1]}
+                                       FROM (
+                                               SELECT ROW_NUMBER() OVER({$overOrder}) AS rowNumber, *
+                                               FROM ({$sql}) {$sub1}
+                                       ) {$sub2}
+                                       WHERE rowNumber BETWEEN {$first} AND {$last}{$postOrder}";
+
+                       return $sql;
+               }
+       }
+
+       /**
+        * If there is a limit clause, parse it, strip it, and pass the remaining
+        * SQL through limitResult() with the appropriate parameters. Not the
+        * prettiest solution, but better than building a whole new parser. This
+        * exists becase there are still too many extensions that don't use dynamic
+        * sql generation.
+        *
+        * @param string $sql
+        * @return array|mixed|string
+        */
+       public function LimitToTopN( $sql ) {
+               // Matches: LIMIT {[offset,] row_count | row_count OFFSET offset}
+               $pattern = '/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
+               if ( preg_match( $pattern, $sql, $matches ) ) {
+                       $row_count = $matches[4];
+                       $offset = $matches[3] ?: $matches[6] ?: false;
+
+                       // strip the matching LIMIT clause out
+                       $sql = str_replace( $matches[0], '', $sql );
+
+                       return $this->limitResult( $sql, $row_count, $offset );
+               }
+
+               return $sql;
+       }
+
+       /**
+        * @return string Wikitext of a link to the server software's web site
+        */
+       public function getSoftwareLink() {
+               return "[{{int:version-db-mssql-url}} MS SQL Server]";
+       }
+
+       /**
+        * @return string Version information from the database
+        */
+       public function getServerVersion() {
+               $server_info = sqlsrv_server_info( $this->mConn );
+               $version = 'Error';
+               if ( isset( $server_info['SQLServerVersion'] ) ) {
+                       $version = $server_info['SQLServerVersion'];
+               }
+
+               return $version;
+       }
+
+       /**
+        * @param string $table
+        * @param string $fname
+        * @return bool
+        */
+       public function tableExists( $table, $fname = __METHOD__ ) {
+               list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
+
+               if ( $db !== false ) {
+                       // remote database
+                       $this->queryLogger->error( "Attempting to call tableExists on a remote table" );
+                       return false;
+               }
+
+               if ( $schema === false ) {
+                       $schema = $this->mSchema;
+               }
+
+               $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
+                       WHERE TABLE_TYPE = 'BASE TABLE'
+                       AND TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table'" );
+
+               if ( $res->numRows() ) {
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Query whether a given column exists in the mediawiki schema
+        * @param string $table
+        * @param string $field
+        * @param string $fname
+        * @return bool
+        */
+       public function fieldExists( $table, $field, $fname = __METHOD__ ) {
+               list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
+
+               if ( $db !== false ) {
+                       // remote database
+                       $this->queryLogger->error( "Attempting to call fieldExists on a remote table" );
+                       return false;
+               }
+
+               $res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
+                       WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
+
+               if ( $res->numRows() ) {
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+
+       public function fieldInfo( $table, $field ) {
+               list( $db, $schema, $table ) = $this->tableName( $table, 'split' );
+
+               if ( $db !== false ) {
+                       // remote database
+                       $this->queryLogger->error( "Attempting to call fieldInfo on a remote table" );
+                       return false;
+               }
+
+               $res = $this->query( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS
+                       WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
+
+               $meta = $res->fetchRow();
+               if ( $meta ) {
+                       return new MssqlField( $meta );
+               }
+
+               return false;
+       }
+
+       /**
+        * Begin a transaction, committing any previously open transaction
+        * @param string $fname
+        */
+       protected function doBegin( $fname = __METHOD__ ) {
+               sqlsrv_begin_transaction( $this->mConn );
+               $this->mTrxLevel = 1;
+       }
+
+       /**
+        * End a transaction
+        * @param string $fname
+        */
+       protected function doCommit( $fname = __METHOD__ ) {
+               sqlsrv_commit( $this->mConn );
+               $this->mTrxLevel = 0;
+       }
+
+       /**
+        * Rollback a transaction.
+        * No-op on non-transactional databases.
+        * @param string $fname
+        */
+       protected function doRollback( $fname = __METHOD__ ) {
+               sqlsrv_rollback( $this->mConn );
+               $this->mTrxLevel = 0;
+       }
+
+       /**
+        * @param string $s
+        * @return string
+        */
+       public function strencode( $s ) {
+               // Should not be called by us
+
+               return str_replace( "'", "''", $s );
+       }
+
+       /**
+        * @param string|int|null|bool|Blob $s
+        * @return string|int
+        */
+       public function addQuotes( $s ) {
+               if ( $s instanceof MssqlBlob ) {
+                       return $s->fetch();
+               } elseif ( $s instanceof Blob ) {
+                       // this shouldn't really ever be called, but it's here if needed
+                       // (and will quite possibly make the SQL error out)
+                       $blob = new MssqlBlob( $s->fetch() );
+                       return $blob->fetch();
+               } else {
+                       if ( is_bool( $s ) ) {
+                               $s = $s ? 1 : 0;
+                       }
+                       return parent::addQuotes( $s );
+               }
+       }
+
+       /**
+        * @param string $s
+        * @return string
+        */
+       public function addIdentifierQuotes( $s ) {
+               // http://msdn.microsoft.com/en-us/library/aa223962.aspx
+               return '[' . $s . ']';
+       }
+
+       /**
+        * @param string $name
+        * @return bool
+        */
+       public function isQuotedIdentifier( $name ) {
+               return strlen( $name ) && $name[0] == '[' && substr( $name, -1, 1 ) == ']';
+       }
+
+       /**
+        * MS SQL supports more pattern operators than other databases (ex: [,],^)
+        *
+        * @param string $s
+        * @return string
+        */
+       protected function escapeLikeInternal( $s ) {
+               return addcslashes( $s, '\%_[]^' );
+       }
+
+       /**
+        * MS SQL requires specifying the escape character used in a LIKE query
+        * or using Square brackets to surround characters that are to be escaped
+        * https://msdn.microsoft.com/en-us/library/ms179859.aspx
+        * Here we take the Specify-Escape-Character approach since it's less
+        * invasive, renders a query that is closer to other DB's and better at
+        * handling square bracket escaping
+        *
+        * @return string Fully built LIKE statement
+        */
+       public function buildLike() {
+               $params = func_get_args();
+               if ( count( $params ) > 0 && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+
+               return parent::buildLike( $params ) . " ESCAPE '\' ";
+       }
+
+       /**
+        * @param string $db
+        * @return bool
+        */
+       public function selectDB( $db ) {
+               try {
+                       $this->mDBname = $db;
+                       $this->query( "USE $db" );
+                       return true;
+               } catch ( Exception $e ) {
+                       return false;
+               }
+       }
+
+       /**
+        * @param array $options An associative array of options to be turned into
+        *   an SQL query, valid keys are listed in the function.
+        * @return array
+        */
+       public function makeSelectOptions( $options ) {
+               $tailOpts = '';
+               $startOpts = '';
+
+               $noKeyOptions = [];
+               foreach ( $options as $key => $option ) {
+                       if ( is_numeric( $key ) ) {
+                               $noKeyOptions[$option] = true;
+                       }
+               }
+
+               $tailOpts .= $this->makeGroupByWithHaving( $options );
+
+               $tailOpts .= $this->makeOrderBy( $options );
+
+               if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
+                       $startOpts .= 'DISTINCT';
+               }
+
+               if ( isset( $noKeyOptions['FOR XML'] ) ) {
+                       // used in group concat field emulation
+                       $tailOpts .= " FOR XML PATH('')";
+               }
+
+               // we want this to be compatible with the output of parent::makeSelectOptions()
+               return [ $startOpts, '', $tailOpts, '', '' ];
+       }
+
+       public function getType() {
+               return 'mssql';
+       }
+
+       /**
+        * @param array $stringList
+        * @return string
+        */
+       public function buildConcat( $stringList ) {
+               return implode( ' + ', $stringList );
+       }
+
+       /**
+        * Build a GROUP_CONCAT or equivalent statement for a query.
+        * MS SQL doesn't have GROUP_CONCAT so we emulate it with other stuff (and boy is it nasty)
+        *
+        * This is useful for combining a field for several rows into a single string.
+        * NULL values will not appear in the output, duplicated values will appear,
+        * and the resulting delimiter-separated values have no defined sort order.
+        * Code using the results may need to use the PHP unique() or sort() methods.
+        *
+        * @param string $delim Glue to bind the results together
+        * @param string|array $table Table name
+        * @param string $field Field name
+        * @param string|array $conds Conditions
+        * @param string|array $join_conds Join conditions
+        * @return string SQL text
+        * @since 1.23
+        */
+       public function buildGroupConcatField( $delim, $table, $field, $conds = '',
+               $join_conds = []
+       ) {
+               $gcsq = 'gcsq_' . $this->mSubqueryId;
+               $this->mSubqueryId++;
+
+               $delimLen = strlen( $delim );
+               $fld = "{$field} + {$this->addQuotes( $delim )}";
+               $sql = "(SELECT LEFT({$field}, LEN({$field}) - {$delimLen}) FROM ("
+                       . $this->selectSQLText( $table, $fld, $conds, null, [ 'FOR XML' ], $join_conds )
+                       . ") {$gcsq} ({$field}))";
+
+               return $sql;
+       }
+
+       /**
+        * Returns an associative array for fields that are of type varbinary, binary, or image
+        * $table can be either a raw table name or passed through tableName() first
+        * @param string $table
+        * @return array
+        */
+       private function getBinaryColumns( $table ) {
+               $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
+               $tableRaw = array_pop( $tableRawArr );
+
+               if ( $this->mBinaryColumnCache === null ) {
+                       $this->populateColumnCaches();
+               }
+
+               return isset( $this->mBinaryColumnCache[$tableRaw] )
+                       ? $this->mBinaryColumnCache[$tableRaw]
+                       : [];
+       }
+
+       /**
+        * @param string $table
+        * @return array
+        */
+       private function getBitColumns( $table ) {
+               $tableRawArr = explode( '.', preg_replace( '#\[([^\]]*)\]#', '$1', $table ) );
+               $tableRaw = array_pop( $tableRawArr );
+
+               if ( $this->mBitColumnCache === null ) {
+                       $this->populateColumnCaches();
+               }
+
+               return isset( $this->mBitColumnCache[$tableRaw] )
+                       ? $this->mBitColumnCache[$tableRaw]
+                       : [];
+       }
+
+       private function populateColumnCaches() {
+               $res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
+                       [
+                               'TABLE_CATALOG' => $this->mDBname,
+                               'TABLE_SCHEMA' => $this->mSchema,
+                               'DATA_TYPE' => [ 'varbinary', 'binary', 'image', 'bit' ]
+                       ] );
+
+               $this->mBinaryColumnCache = [];
+               $this->mBitColumnCache = [];
+               foreach ( $res as $row ) {
+                       if ( $row->DATA_TYPE == 'bit' ) {
+                               $this->mBitColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
+                       } else {
+                               $this->mBinaryColumnCache[$row->TABLE_NAME][$row->COLUMN_NAME] = $row;
+                       }
+               }
+       }
+
+       /**
+        * @param string $name
+        * @param string $format
+        * @return string
+        */
+       function tableName( $name, $format = 'quoted' ) {
+               # Replace reserved words with better ones
+               switch ( $name ) {
+                       case 'user':
+                               return $this->realTableName( 'mwuser', $format );
+                       default:
+                               return $this->realTableName( $name, $format );
+               }
+       }
+
+       /**
+        * call this instead of tableName() in the updater when renaming tables
+        * @param string $name
+        * @param string $format One of quoted, raw, or split
+        * @return string
+        */
+       function realTableName( $name, $format = 'quoted' ) {
+               $table = parent::tableName( $name, $format );
+               if ( $format == 'split' ) {
+                       // Used internally, we want the schema split off from the table name and returned
+                       // as a list with 3 elements (database, schema, table)
+                       $table = explode( '.', $table );
+                       while ( count( $table ) < 3 ) {
+                               array_unshift( $table, false );
+                       }
+               }
+               return $table;
+       }
+
+       /**
+        * Delete a table
+        * @param string $tableName
+        * @param string $fName
+        * @return bool|ResultWrapper
+        * @since 1.18
+        */
+       public function dropTable( $tableName, $fName = __METHOD__ ) {
+               if ( !$this->tableExists( $tableName, $fName ) ) {
+                       return false;
+               }
+
+               // parent function incorrectly appends CASCADE, which we don't want
+               $sql = "DROP TABLE " . $this->tableName( $tableName );
+
+               return $this->query( $sql, $fName );
+       }
+
+       /**
+        * Called in the installer and updater.
+        * Probably doesn't need to be called anywhere else in the codebase.
+        * @param bool|null $value
+        * @return bool|null
+        */
+       public function prepareStatements( $value = null ) {
+               $old = $this->mPrepareStatements;
+               if ( $value !== null ) {
+                       $this->mPrepareStatements = $value;
+               }
+
+               return $old;
+       }
+
+       /**
+        * Called in the installer and updater.
+        * Probably doesn't need to be called anywhere else in the codebase.
+        * @param bool|null $value
+        * @return bool|null
+        */
+       public function scrollableCursor( $value = null ) {
+               $old = $this->mScrollableCursor;
+               if ( $value !== null ) {
+                       $this->mScrollableCursor = $value;
+               }
+
+               return $old;
+       }
+}
index ceed7da..361fc50 100644 (file)
@@ -20,6 +20,8 @@
  * @file
  * @ingroup Database
  */
+use Wikimedia\Rdbms\DBMasterPos;
+use Wikimedia\Rdbms\MySQLMasterPos;
 
 /**
  * Database abstraction object for MySQL.
index 75cc97c..b54ada7 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup Database
  */
 use Wikimedia\WaitConditionLoop;
+use Wikimedia\Rdbms\Blob;
+use Wikimedia\Rdbms\PostgresBlob;
 
 /**
  * @ingroup Database
index a4b2df0..bf61671 100644 (file)
@@ -21,6 +21,7 @@
  * @file
  * @ingroup Database
  */
+use Wikimedia\Rdbms\Blob;
 
 /**
  * @ingroup Database
index f1613eb..591c797 100644 (file)
@@ -24,7 +24,9 @@
  * @ingroup Database
  */
 use Wikimedia\ScopedCallback;
+use Wikimedia\Rdbms\Blob;
 use Wikimedia\Rdbms\LikeMatch;
+use Wikimedia\Rdbms\DBMasterPos;
 
 /**
  * Basic database interface for live and lazy-loaded relation database handles
index eda0ff3..2f79ea9 100644 (file)
@@ -1,4 +1,7 @@
 <?php
+
+namespace Wikimedia\Rdbms;
+
 /**
  * An object representing a master or replica DB position in a replicated setup.
  *
index 7b49ce9..06776fe 100644 (file)
@@ -1,4 +1,9 @@
 <?php
+
+namespace Wikimedia\Rdbms;
+
+use InvalidArgumentException;
+
 /**
  * DBMasterPos class for MySQL/MariaDB
  *
diff --git a/includes/libs/rdbms/database/resultwrapper/IResultWrapper.php b/includes/libs/rdbms/database/resultwrapper/IResultWrapper.php
new file mode 100644 (file)
index 0000000..dc89a2d
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+namespace Wikimedia\Rdbms;
+
+use Iterator;
+use DBUnexpectedError;
+use stdClass;
+
+/**
+ * Result wrapper for grabbing data queried from an IDatabase object
+ *
+ * Note that using the Iterator methods in combination with the non-Iterator
+ * DB result iteration functions may cause rows to be skipped or repeated.
+ *
+ * By default, this will use the iteration methods of the IDatabase handle if provided.
+ * Subclasses can override methods to make it solely work on the result resource instead.
+ * If no database is provided, and the subclass does not override the DB iteration methods,
+ * then a RuntimeException will be thrown when iteration is attempted.
+ *
+ * The result resource field should not be accessed from non-Database related classes.
+ * It is database class specific and is stored here to associate iterators with queries.
+ *
+ * @ingroup Database
+ */
+interface IResultWrapper extends Iterator {
+       /**
+        * Get the number of rows in a result object
+        *
+        * @return int
+        */
+       public function numRows();
+
+       /**
+        * Fetch the next row from the given result object, in object form. Fields can be retrieved with
+        * $row->fieldname, with fields acting like member variables. If no more rows are available,
+        * false is returned.
+        *
+        * @return stdClass|bool
+        * @throws DBUnexpectedError Thrown if the database returns an error
+        */
+       public function fetchObject();
+
+       /**
+        * Fetch the next row from the given result object, in associative array form. Fields are
+        * retrieved with $row['fieldname']. If no more rows are available, false is returned.
+        *
+        * @return array|bool
+        * @throws DBUnexpectedError Thrown if the database returns an error
+        */
+       public function fetchRow();
+
+       /**
+        * Change the position of the cursor in a result object.
+        * See mysql_data_seek()
+        *
+        * @param int $row
+        */
+       public function seek( $row );
+
+       /**
+        * Free a result object
+        *
+        * This either saves memory in PHP (buffered queries) or on the server (unbuffered queries).
+        * In general, queries are not large enough in result sets for this to be worth calling.
+        */
+       public function free();
+
+       /**
+        * @return stdClass|array|bool
+        */
+       public function current();
+
+       /**
+        * @return int
+        */
+       public function key();
+
+       /**
+        * @return stdClass
+        */
+       function next();
+}
index 53109c8..88e7cdd 100644 (file)
@@ -1,4 +1,7 @@
 <?php
+
+use Wikimedia\Rdbms\IResultWrapper;
+
 /**
  * Result wrapper for grabbing data queried from an IDatabase object
  *
@@ -15,7 +18,7 @@
  *
  * @ingroup Database
  */
-class ResultWrapper implements Iterator {
+class ResultWrapper implements IResultWrapper {
        /** @var resource|array|null Optional underlying result handle for subclass usage */
        public $result;
 
@@ -45,54 +48,22 @@ class ResultWrapper implements Iterator {
                }
        }
 
-       /**
-        * Get the number of rows in a result object
-        *
-        * @return int
-        */
        public function numRows() {
                return $this->getDB()->numRows( $this );
        }
 
-       /**
-        * Fetch the next row from the given result object, in object form. Fields can be retrieved with
-        * $row->fieldname, with fields acting like member variables. If no more rows are available,
-        * false is returned.
-        *
-        * @return stdClass|bool
-        * @throws DBUnexpectedError Thrown if the database returns an error
-        */
        public function fetchObject() {
                return $this->getDB()->fetchObject( $this );
        }
 
-       /**
-        * Fetch the next row from the given result object, in associative array form. Fields are
-        * retrieved with $row['fieldname']. If no more rows are available, false is returned.
-        *
-        * @return array|bool
-        * @throws DBUnexpectedError Thrown if the database returns an error
-        */
        public function fetchRow() {
                return $this->getDB()->fetchRow( $this );
        }
 
-       /**
-        * Change the position of the cursor in a result object.
-        * See mysql_data_seek()
-        *
-        * @param int $row
-        */
        public function seek( $row ) {
                $this->getDB()->dataSeek( $this, $row );
        }
 
-       /**
-        * Free a result object
-        *
-        * This either saves memory in PHP (buffered queries) or on the server (unbuffered queries).
-        * In general, queries are not large enough in result sets for this to be worth calling.
-        */
        public function free() {
                if ( $this->db ) {
                        $this->db->freeResult( $this );
@@ -121,9 +92,6 @@ class ResultWrapper implements Iterator {
                $this->currentRow = null;
        }
 
-       /**
-        * @return stdClass|array|bool
-        */
        function current() {
                if ( is_null( $this->currentRow ) ) {
                        $this->next();
@@ -132,16 +100,10 @@ class ResultWrapper implements Iterator {
                return $this->currentRow;
        }
 
-       /**
-        * @return int
-        */
        function key() {
                return $this->pos;
        }
 
-       /**
-        * @return stdClass
-        */
        function next() {
                $this->pos++;
                $this->currentRow = $this->fetchObject();
index bd90330..d394692 100644 (file)
@@ -1,19 +1,21 @@
 <?php
-/**
- * Utility class
- * @ingroup Database
- *
- * This allows us to distinguish a blob from a normal string and an array of strings
- */
-class Blob {
+
+namespace Wikimedia\Rdbms;
+
+class Blob implements IBlob {
        /** @var string */
        protected $mData;
 
-       function __construct( $data ) {
+       /**
+        * @param $data string
+        */
+       public function __construct( $data ) {
                $this->mData = $data;
        }
 
-       function fetch() {
+       public function fetch() {
                return $this->mData;
        }
 }
+
+class_alias( 'Wikimedia\Rdbms\Blob', 'Blob' );
diff --git a/includes/libs/rdbms/encasing/IBlob.php b/includes/libs/rdbms/encasing/IBlob.php
new file mode 100644 (file)
index 0000000..b1d7aae
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+namespace Wikimedia\Rdbms;
+
+/**
+ * Wrapper allowing us to distinguish a blob from a normal string and an array of strings
+ * @ingroup Database
+ */
+interface IBlob {
+       /**
+        * @return string
+        */
+       public function fetch();
+}
index 35be65c..aacdf40 100644 (file)
@@ -1,5 +1,13 @@
 <?php
+
+namespace Wikimedia\Rdbms;
+
 class MssqlBlob extends Blob {
+       /** @noinspection PhpMissingParentConstructorInspection */
+
+       /**
+        * @param string $data
+        */
        public function __construct( $data ) {
                if ( $data instanceof MssqlBlob ) {
                        return $data;
index cc52336..7994b73 100644 (file)
@@ -1,4 +1,7 @@
 <?php
+
+namespace Wikimedia\Rdbms;
+
 class PostgresBlob extends Blob {
 
 }
index d6cff78..4e6f6b0 100644 (file)
@@ -29,7 +29,6 @@ use DBConnRef;
 use MaintainableDBConnRef;
 use DBError;
 use DBAccessError;
-use DBMasterPos;
 use DBTransactionError;
 use DBExpectedError;
 use Exception;
index 3442b73..900a79c 100644 (file)
@@ -27,6 +27,7 @@ use Wikimedia\Rdbms\TransactionProfiler;
 use Wikimedia\Rdbms\ILoadMonitor;
 use Wikimedia\Rdbms\DatabaseDomain;
 use Wikimedia\Rdbms\ILoadBalancer;
+use Wikimedia\Rdbms\DBMasterPos;
 
 /**
  * Database connection, tracking, load balancing, and transaction manager for a cluster
@@ -490,7 +491,10 @@ class LoadBalancer implements ILoadBalancer {
                $key = $this->srvCache->makeGlobalKey( __CLASS__, 'last-known-pos', $server );
                /** @var DBMasterPos $knownReachedPos */
                $knownReachedPos = $this->srvCache->get( $key );
-               if ( $knownReachedPos && $knownReachedPos->hasReached( $this->mWaitForPos ) ) {
+               if (
+                       $knownReachedPos instanceof DBMasterPos &&
+                       $knownReachedPos->hasReached( $this->mWaitForPos )
+               ) {
                        $this->replLogger->debug( __METHOD__ .
                                ": replica DB $server known to be caught up (pos >= $knownReachedPos)." );
                        return true;
index 833e38b..64a6aec 100644 (file)
@@ -19,6 +19,8 @@
  * @ingroup RevisionDelete
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Abstract base class for a list of deletable items. The list class
  * needs to be able to make a query from a set of identifiers to pull
@@ -255,7 +257,8 @@ abstract class RevDelList extends RevisionListBase {
                $status->merge( $this->doPreCommitUpdates() );
                if ( !$status->isOK() ) {
                        // Fatal error, such as no configured archive directory or I/O failures
-                       wfGetLBFactory()->rollbackMasterChanges( __METHOD__ );
+                       $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                       $lbFactory->rollbackMasterChanges( __METHOD__ );
                        return $status;
                }
 
index bf535a6..2d6ba4a 100644 (file)
@@ -23,6 +23,8 @@
  * @ingroup SpecialPage
  */
 
+use Mediawiki\MediaWikiServices;
+
 /**
  * A special page that allows users to export pages in a XML file
  *
@@ -374,7 +376,7 @@ class SpecialExport extends SpecialPage {
                        $buffer = WikiExporter::BUFFER;
                } else {
                        // Use an unbuffered query; histories may be very long!
-                       $lb = wfGetLBFactory()->newMainLB();
+                       $lb = MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->newMainLB();
                        $db = $lb->getConnection( DB_REPLICA );
                        $buffer = WikiExporter::STREAM;
 
index 319b5d4..aada013 100644 (file)
@@ -292,9 +292,12 @@ EOD;
                        $this->readFile( $file );
                }
 
-               // Legacy aliases
+               // Legacy aliases (1.28)
                $this->forceClassPath( 'DatabaseBase',
                        $this->basepath . '/includes/libs/rdbms/database/Database.php' );
+               // Legacy aliases (1.29)
+               $this->forceClassPath( 'Blob',
+                       $this->basepath . '/includes/libs/rdbms/encasing/Blob.php' );
        }
 }
 
index 44229b8..6b51db5 100644 (file)
@@ -81,6 +81,6 @@ class InterwikiSearchResultWidget implements SearchResultWidget {
                        default:
                                return "<div class='iw-result__title'>{$link} {$redirect}</div>" .
                                        "<div class='iw-result__content'>{$snippet}</div>";
-               };
+               }
        }
 }
index 21dbddf..2ef9e8b 100644 (file)
        "saveusergroups": "احفظ مجموعات {{GENDER:$1|المستخدم|المستخدمة}}",
        "userrights-groupsmember": "عضو في:",
        "userrights-groupsmember-auto": "عضو ضمني في:",
-       "userrights-groups-help": "يمكنك تغيير المجموعات التي ينتمي هذا المستخدم إليها:\n* يعني الصندوق المعلم أن المستخدم في هذه المجموعة.\n* يعني الصندوق غير المعلم أن المستخدم ليس في هذه المجموعة.\n* تعني علامة * عدم إمكانية إزالة المجموعة متى ما أضفتها، أو العكس.",
+       "userrights-groups-help": "يمكنك تغيير المجموعات التي ينتمي هذا المستخدم إليها:\n* يعني الصندوق المعلم أن المستخدم في هذه المجموعة.\n* يعني الصندوق غير المعلم أن المستخدم ليس في هذه المجموعة.\n* تعني علامة * عدم إمكانية إزالة المجموعة متى ما أضفتها، أو العكس.\n* تعن علامة # أنه يمكنك فقط تحديد تاريخ الانتهاء لهذه المجموعة؛ لكن لا يمكنك تقديمه بعد تحديده.",
        "userrights-reason": "السبب:",
        "userrights-no-interwiki": "أنت لا تمتلك الصلاحية لتعديل صلاحيات المستخدمين على الويكيات الأخرى.",
        "userrights-nodatabase": "قاعدة البيانات $1 غير موجودة أو ليست محلية.",
        "userrights-expiry-options": "1 يوم:1 day,1 أسبوع:1 week,1 شهر:1 month,3 شهور:3 months,6 شهور:6 months,1 سنة:1 year",
        "userrights-invalid-expiry": "تاريخ انتهاء المجموعة \"$1\" غير صحيح.",
        "userrights-expiry-in-past": "تاريخ انتهاء المجموعة \"$1\" هو في الماضي.",
+       "userrights-cannot-shorten-expiry": "أنت لا يمكنك تقديم تاريخ الانتهاء للمجموعة \"$1\". فقط المستخدمون الذين يمتلكون السماح لإضافة وإزالة هذه المجموعة يمكنهم تقديم تواريخ الانتهاء.",
        "userrights-conflict": "تضارب في تغيير صلاحيات المستخدم! الرجاء مراجعة تغييراتك مجدّدا وتأكيدها.",
        "group": "المجموعة:",
        "group-user": "مستخدمون",
index 2298288..4f8b695 100644 (file)
        "search-interwiki-caption": "Proyeutos hermanos",
        "search-interwiki-default": "Resultaos de $1:",
        "search-interwiki-more": "(más)",
+       "search-interwiki-more-results": "más resultaos",
        "search-relatedarticle": "Rellacionáu",
        "searchrelated": "rellacionáu",
        "searchall": "toos",
        "editusergroup": "Cargar los grupos d'usuariu",
        "editinguser": "Camudando los permisos {{GENDER:$1|del usuariu|de la usuaria}} <strong>[[User:$1|$1]]</strong> $2",
        "viewinguserrights": "Viendo los permisos {{GENDER:$1|del usuariu|de la usuaria}} <strong>[[User:$1|$1]]</strong> $2",
-       "userrights-editusergroup": "Editar los grupos d'usuariu",
-       "userrights-viewusergroup": "Ver los grupos d'usuariu",
+       "userrights-editusergroup": "Editar los grupos {{GENDER:$1|del usuariu|de la usuaria}}",
+       "userrights-viewusergroup": "Ver los grupos {{GENDER:$1|del usuariu|de la usuaria}}",
        "saveusergroups": "Guardar los grupos {{GENDER:$1|del usuariu|de la usuaria}}",
        "userrights-groupsmember": "Miembru de:",
        "userrights-groupsmember-auto": "Miembru implícitu de:",
-       "userrights-groups-help": "Pues camudar los grupos a los que pertenez esti usuariu.\n* Un caxellu marcáu significa que l'usuariu ta nesi grupu.\n* Un caxellu non marcáu significa que l'usuariu nun ta nesi grupu.\n* Un * indica que nun pues eliminalu del grupu una vegada tea inxeríu, o viceversa.",
+       "userrights-groups-help": "Pues camudar los grupos a los que pertenez esti usuariu.\n* Un caxellu marcáu significa que l'usuariu ta nesi grupu.\n* Un caxellu non marcáu significa que l'usuariu nun ta nesi grupu.\n* Un * indica que nun pues desaniciar el grupu una vegada tea inxeríu, o viceversa.\n* Un # indica que namái puede atrasase la fecha de caducidá d'esti grupu; nun puede adelantase.",
        "userrights-reason": "Motivu:",
        "userrights-no-interwiki": "Nun tienes permisu pa editar los derechos d'usuariu n'otres wikis.",
        "userrights-nodatabase": "La base de datos $1 nun esiste o nun ye llocal.",
        "userrights-expiry-options": "1 día:1 day,1 selmana:1 week,1 mes:1 month,3 meses:3 months,6 meses:6 months,1 añu:1 year",
        "userrights-invalid-expiry": "La hora de caducidá del grupu «$1» nun ye válida.",
        "userrights-expiry-in-past": "La hora de caducidá del grupu «$1» ta nel pasáu",
+       "userrights-cannot-shorten-expiry": "Nun puedes adelantar la caducidá del grupu «$1». Sólo los usuarios con permisu p'amestar y desaniciar esti grupu pueden adelantar les dates de caducidá.",
        "userrights-conflict": "¡Conflictu de cambiu de permisos d'usuariu! Por favor, revise y confirme los cambios.",
        "group": "Grupu:",
        "group-user": "Usuarios",
        "mw-widgets-titleinput-description-new-page": "la páxina inda nun esiste",
        "mw-widgets-titleinput-description-redirect": "redirixir a $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Amestar una categoría...",
+       "mw-widgets-usersmultiselect-placeholder": "Amestar más...",
        "sessionmanager-tie": "Nun puen combinase dellos tipos de solicitú d'identificación: $1.",
        "sessionprovider-generic": "sesiones $1",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "sesiones basaes en cookies",
index 6109b86..4fbeabd 100644 (file)
        "rev-deleted-user": "(імя ўдзельніка выдаленае)",
        "rev-deleted-event": "(падрабязнасьці выдаленыя з журнала падзеяў)",
        "rev-deleted-user-contribs": "[імя ўдзельніка альбо IP-адрас выдалены — рэдагаваньне схаванае з унёску]",
-       "rev-deleted-text-permission": "Гэтая вэрсія старонкі была '''выдаленая'''.\nМагчыма, падрабязнасьці могуць быць знойдзеныя ў [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале выдаленьняў].",
+       "rev-deleted-text-permission": "Гэтая вэрсія старонкі была <strong>выдаленая</strong>.\nПадрабязнасьці могуць быць знойдзеныя ў [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале выдаленьняў].",
        "rev-suppressed-text-permission": "Гэтая вэрсія старонкі была <strong>схаваная</strong>.\nПадрабязнасьці могуць быць знойдзеныя ў [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} журнале хаваньняў].",
        "rev-deleted-text-unhide": "Гэтая вэрсія старонкі была '''выдаленая'''.\nПадрабязнасьці могуць быць знойдзеныя ў [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале выдаленьняў].\nВы можаце [$1 праглядзець гэтую вэрсію], калі жадаеце.",
        "rev-suppressed-text-unhide": "Гэтая вэрсія старонкі была '''схаваная'''.\nПадрабязнасьці могуць быць знойдзеныя ў [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} журнале хаваньняў].\nВы можаце [$1 праглядзець гэтую вэрсію], калі жадаеце.",
        "rcfilters-filter-pageedits-label": "Рэдагаваньні старонкі",
        "rcfilters-filter-pageedits-description": "Рэдагаваньні вікізьместу, абмеркаваньняў, апісаньняў катэгорыяў…",
        "rcfilters-filter-newpages-label": "Стварэньні старонак",
+       "rcfilters-filter-newpages-description": "Праўкі, якімі створаныя новыя старонкі.",
+       "rcfilters-filter-categorization-label": "Зьмены катэгорыяў",
+       "rcfilters-filter-categorization-description": "Запісы пра дадаваньне і выдаленьне старонак з катэгорыяў.",
+       "rcfilters-filter-logactions-label": "Журнальныя дзеяньні",
+       "rcfilters-filter-logactions-description": "Адміністрацыйныя дзеяньні, стварэньні рахункаў, выдаленьні старонак, загрузкі…",
        "rcnotefrom": "Ніжэй {{PLURAL:$5|знаходзіцца зьмена|знаходзяцца зьмены}} з <strong>$4 $3</strong> (да <strong>$1</strong> на старонку).",
        "rclistfrom": "Паказаць зьмены з $2 $3",
        "rcshowhideminor": "$1 дробныя праўкі",
        "apisandbox-sending-request": "Адпраўка API-запыту…",
        "apisandbox-loading-results": "Атрымліваем API-вынікі…",
        "apisandbox-results-error": "Адбылася памылка пры загрузцы адказу на API-запыт: $1.",
+       "apisandbox-request-selectformat-label": "Паказаць зьвесткі запыту як:",
        "apisandbox-request-url-label": "URL-адрас запыту:",
        "apisandbox-request-time": "Час запыту: {{PLURAL:$1|$1 мс}}",
        "apisandbox-results-fixtoken": "Выпраўце токен і паўтарыце адпраўку",
index e7d99eb..2b5c7fc 100644 (file)
        "red-link-title": "$1 (چونو بألگئ یی نیدٙئس)",
        "nstab-main": "بلگه",
        "nstab-user": "صفحه کاربر",
+       "nstab-media": "بلگأ ڤارسگأري",
        "nstab-special": "بألگه ڤیجه",
        "nstab-project": "صفحه پروژه",
        "nstab-image": "فایل",
        "mainpage-nstab": "سأرآسوٙنە",
        "error": "خطا",
        "databaseerror-query": "جوستکاری: $1",
+       "databaseerror-error": "خطا: $1",
        "badtitle": "عنوان بد",
        "badtitletext": "عنوان درخواستی نامعتبر، خالی، یا عنوانی بین زبانی یا بین‌ویکی‌ای با پیوند نادرسته\nو ممکنه دارای یک یا چند کاراکتر بوه که در عنوان مربوط نوا زش استفاده کنین",
        "viewsource": "مشاهده منبع",
        "viewsourcetext": "ایسا ترین بوینین وکپی کنین منبع ای صفحه را:",
+       "welcomeuser": "خۈش أڤوڌين،$1!",
        "yourname": "نام کاربر:",
        "userlogin-yourname": "نوم کارياري",
        "userlogin-yourname-ph": "نوم کاریاريتونأ بزنين",
        "createacct-yourpasswordagain": "پشت راسدکاري رازينإ گوڤأرتن",
        "createacct-yourpasswordagain-ph": "ز نۉ رازينإ گوڤأرتن نأ بزأ",
        "userlogin-remembermypassword": "مۈنإ مإن سامۈنإ ڤاڌار",
+       "yourdomainname": "پوشگر ايسا:",
        "login": "اویدن به سیستم",
        "nav-login-createaccount": "اویدن به سیستم",
        "userlogin": "اویدن به سیستم / درست کردن حساب کاربری",
        "userlogin-helplink2": "هومياري کردن سي ڤامإن أڤوڌن",
        "createacct-emailoptional": "تيرنشۈن أنجومانامأ",
        "createacct-email-ph": "تيرنشۈن أنجومانامأ تۈنأ بزنين",
+       "createaccountreason": "دلیل:",
+       "createacct-reason": "دلیل",
        "createacct-submit": "هساڤ خوتۈنإ راسد کونين",
+       "createacct-another-submit": "راسد کردن هساڤ کارياري",
        "createacct-benefit-body1": "{{PLURAL:$1|ڤيرایشد|ڤيرایشدا}}",
        "createacct-benefit-body2": "{{PLURAL:$1|بألگأ|بألگإ آ}}",
        "createacct-benefit-body3": "تازأ{{PLURAL:$1|هوميار|هوميارا}}",
        "noemail": "وجود نداره نشانی امیل ضبط وابده زه کاریر \"$1\".",
        "passwordsent": "یه رمز تازه ارسال وابید به نشانی امیل ثبت وابده سی \"$1\".\nلطفا بعد از دریافت آن داخل سیستم بوین.",
        "eauthentsent": "یه ایمیل سی تایید آدرس ایمیل به آدرس مورنظر ارسال وابید. قبل زه یو که ایمیل دیگری قابل ارسال به این آدرس بوه، وا دستورهایی که در آن ایمیل اویده را جهت تأیید ای مساله که ای آدرس مال ایسانه اجرا کنین.",
+       "accountcreated": "هساڤ راسد ڤابي",
        "loginlanguagelabel": "زۈن:$1",
        "pt-login": "ڤامین اوڤیڌن",
        "pt-login-button": "ڤامین اوڤیڌن",
        "pt-createaccount": "راسد کردن هساڤ کارياري",
        "pt-userlogout": "ز سامۈنإ درأڤوڌن",
+       "changepassword": "آلشد کردن رازينإ گوڤأرتن",
        "retypenew": "تایپ دوباره رمز:",
+       "botpasswords-label-appid": "نوم بوت:",
        "botpasswords-label-create": "راس كردن",
        "botpasswords-label-cancel": "أنجومشيڤ کردن",
        "botpasswords-label-delete": "پاکسا کردن",
        "rev-delundel": "آلشد هال و بال ديإن",
        "rev-showdeleted": "دياري کردن",
        "revdelete-show-file-submit": "هأرإ",
+       "revdelete-log": "دلیل:",
+       "mergehistory-from": "بألگإ سرچشمأ:",
+       "mergehistory-reason": "دلیل:",
        "history-title": "دڤارتإ دیئن ڤيرگار $1",
        "difference-title": "فرخ مإنجقا ڤانإیريا \"$1\"",
        "lineno": "سطر $1:",
        "search-redirect": "(ڤاگردۈني ز $1)",
        "search-section": "(بهرجا $1)",
        "search-suggest": "منزۈرت یو بي:$1",
+       "search-interwiki-more": "(بيشدر)",
        "searchall": "همه",
        "search-nonefound": "هیژ نتیجه یی وا پی جست تو یکی نئ.",
+       "powersearch-toggleall": "همأ",
+       "powersearch-togglenone": "هيش کوم",
        "preferences": "اولویتها",
        "mypreferences": "خوصوٙیات هأنی",
+       "prefs-skin": "پۈسدأ",
+       "skin-preview": "پيش سإیل",
+       "prefs-watchlist": "سإیل برگ",
+       "prefs-editwatchlist": "ڤيرایشد سإیل برگ",
+       "prefs-misc": "شيڤسدن",
+       "prefs-resetpass": "آلشد کردن رازينإ گوڤأرتن",
+       "saveprefs": "إمایإ کردن",
+       "searchresultshead": "پی جۈري",
+       "stub-threshold-sample-link": "نمۈنأ",
+       "timezoneregion-africa": "إفرقا",
+       "timezoneregion-america": "إمرکا",
+       "timezoneregion-asia": "آسيا",
        "yourrealname": "نام واقعی:",
        "prefs-help-realname": "ذکر نام واقعی اختیاریه ایر تصمیم به گدن بگیرین هنگام ارجاع به آثارتو و انتساب هونو به ایسا زه نام واقعیتو استفاده ابوه",
        "grouppage-sysop": "{{ns:project}}:مدیران",
index 92d8c9f..36f618f 100644 (file)
        "searcharticle": "Idi",
        "history": "Historija stranice",
        "history_short": "Historija",
+       "history_small": "historija",
        "updatedmarker": "promjene od moje posljednje posjete",
        "printableversion": "Za štampanje",
        "permalink": "Trajni link",
        "views": "Pregledi",
        "toolbox": "Alati",
        "tool-link-userrights": "Promijeni {{GENDER:$1|korisničke}} grupe",
+       "tool-link-userrights-readonly": "Vidi {{GENDER:$1|korisničke}} grupe",
        "tool-link-emailuser": "Pošalji e-poruku {{GENDER:$1|korisniku|korisnici}}",
        "userpage": "Pogledaj korisničku stranicu",
        "projectpage": "Pogledaj stranicu projekta",
        "virus-scanfailed": "skeniranje nije uspjelo (code $1)",
        "virus-unknownscanner": "nepoznati anti-virus program:",
        "logouttext": "'''Sad ste odjavljeni.'''\n\nObratite pažnju da neke stranice mogu nastaviti da se prikazuju kao da ste još uvijek prijavljeni, dok ne očistite keš svog preglednika.",
+       "cannotlogoutnow-title": "Odjava trenutno nije moguća",
        "welcomeuser": "Dobro došli, $1",
        "welcomecreation-msg": "Vaš nalog je napravljen.\nNe zaboravite da prilagodite sebi svoja [[Special:Preferences|{{SITENAME}} podešavanja]].",
        "yourname": "Korisničko ime:",
        "createacct-yourpasswordagain-ph": "Unesite lozinku opet",
        "userlogin-remembermypassword": "Ostavi me prijavljenog/-u",
        "userlogin-signwithsecure": "Koristite sigurnu konekciju",
+       "cannotlogin-title": "Prijava nije moguća",
+       "cannotlogin-text": "Prijava nija moguća.",
+       "cannotloginnow-title": "Prijava trenutno nije moguća",
+       "cannotcreateaccount-title": "Pravljenje računa nije moguće",
+       "cannotcreateaccount-text": "Direktno pravljenje računa nije omogućeno na ovom wikiju.",
        "yourdomainname": "Vaš domen:",
        "password-change-forbidden": "Ne možete da promjenite lozinku na ovom wikiju.",
        "externaldberror": "Došlo je do greške pri vanjskoj autorizaciji baze podataka ili vam nije dopušteno osvježavanje Vašeg vanjskog korisničkog računa.",
        "login": "Prijavi me",
+       "login-security": "Potvrdite svoj identitet",
        "nav-login-createaccount": "Prijavi se / Registruj se",
        "userlogin": "Prijavi se / Registruj se",
        "userloginnocreate": "Prijavi se",
        "createacct-email-ph": "Unesite Vašu adresu e-pоšte",
        "createacct-another-email-ph": "Unesite adresu e-pošte",
        "createaccountmail": "Koristite privremenu, slučajno stvorenu lozinku i pošaljite na navedenu adrеsu e-pošte",
+       "createaccountmail-help": "Može se koristiti da se nekome napravi račun bez da se sazna lozinka.",
        "createacct-realname": "Pravo ime (opcionalno)",
        "createaccountreason": "Razlog:",
        "createacct-reason": "Razlog",
        "createacct-reason-ph": "Zašto pravite još jedan korisnički račun?",
+       "createacct-reason-help": "Poruka koja se prikazuje u zapisniku stvaranja korisničkih računa",
        "createacct-submit": "Napravite svoj korisnički račun",
        "createacct-another-submit": "Napravi korisnički račun",
+       "createacct-continue-submit": "Nastavi sa stvaranjem računa",
+       "createacct-another-continue-submit": "Nastavi sa stvaranjem računa",
        "createacct-benefit-heading": "{{GRAMMAR:akuzativ|{{SITENAME}}}} stvaraju ljudi poput Vas.",
        "createacct-benefit-body1": "{{PLURAL:$1|izmjena|izmjene}}",
        "createacct-benefit-body2": "{{PLURAL:$1|stranica|stranice|stranica}}",
        "nocookiesnew": "Korisnički nalog je napravljen, ali niste prijavljeni.\n{{SITENAME}} koristi kolačiće (cookies) da bi se korisnici prijavili.\nVi ste isključili kolačiće na Vašem računaru.\nMolimo Vas da ih uključite, a onda se prijavite sa svojim novim korisničkim imenom i lozinkom.",
        "nocookieslogin": "{{SITENAME}} koristi kolačiće (''cookies'') da bi se korisnici prijavili.  Vi ste onemogućili kolačiće na Vašem kompjuteru.  Molimo Vas da ih omogućite i da pokušate ponovo sa prijavom.",
        "nocookiesfornew": "Korisnički račun nije napravljen, jer nismo mogli da potvrdimo njegov izvor.\nProvjerite da li su cookies omogućeni, ponovo učitajte ovu stranicu i pokušajte ponovo.",
+       "createacct-loginerror": "Račun je uspješno napravljen, ali Vas nije bilo moguće automatski prijaviti. Prijavite se [[Special:UserLogin|ručno]].",
        "noname": "Niste izabrali ispravno korisničko ime.",
        "loginsuccesstitle": "Prijavljen",
        "loginsuccess": "<strong>Prijavili ste se na {{GRAMMAR:akuzativ|{{SITENAME}}}} kao \"$1\".</strong>",
        "createacct-another-realname-tip": "Pravo ime nije obavezno.\nAko izaberete da date ime, biće korišteno za pripisivanje za vaš rad.",
        "pt-login": "Prijavi me",
        "pt-login-button": "Prijavi me",
+       "pt-login-continue-button": "Nastavi prijavljivanje",
        "pt-createaccount": "Napravi korisnički račun",
        "pt-userlogout": "Odjavi me",
        "php-mail-error-unknown": "Nepoznata greška u PHP funkciji mail()",
        "resetpass_submit": "Postavi lozinku i prijavi se",
        "changepassword-success": "Vaša lozinka je promijenjena.",
        "changepassword-throttled": "Previše puta ste se pokušali prijaviti.\nMolimo Vas da sačekate $1 prije nego što pokušate ponovo.",
+       "botpasswords": "Lozinke botova",
+       "botpasswords-disabled": "Lozinke botova su onemogućene.",
+       "botpasswords-no-central-id": "Da biste koristili lozinke botova, morate biti prijavljeni na središnji račun.",
+       "botpasswords-existing": "Postojeće lozinke botova",
+       "botpasswords-createnew": "Napravi novu lozinku bota",
+       "botpasswords-editexisting": "Uredi postojeću lozinku bota",
+       "botpasswords-label-appid": "Ime bota:",
+       "botpasswords-label-create": "Napravi",
+       "botpasswords-label-update": "Ažuriraj",
+       "botpasswords-label-cancel": "Otkaži",
+       "botpasswords-label-delete": "Obriši",
+       "botpasswords-label-resetpassword": "Ponovo postavi lozinku",
+       "botpasswords-label-grants": "Primjenjive dozvole:",
+       "botpasswords-label-grants-column": "Odobreno",
+       "botpasswords-bad-appid": "Ime bota \"$1\" nije ispravno.",
+       "botpasswords-insert-failed": "Ne mogu dodati bota pod nazivom \"$1\". Možda je već dodano?",
+       "botpasswords-update-failed": "Ne mogu ažurirati bota pod nazivom \"$1\". Možda je obrisan?",
+       "botpasswords-created-title": "Napravljena lozinka bota",
+       "botpasswords-created-body": "Napravljena lozinka za bota \"$1\" korisnika \"$2\".",
+       "botpasswords-updated-title": "Ažurirana lozinka bota",
+       "botpasswords-updated-body": "Ažurirana lozinka za bota \"$1\" korisnika \"$2\".",
+       "botpasswords-deleted-title": "Obrisana lozinka bota",
        "resetpass_forbidden": "Lozinke ne mogu biti promijenjene",
+       "resetpass_forbidden-reason": "Lozinke ne mogu biti promijenjene: $1",
        "resetpass-no-info": "Morate biti prijavljeni da biste pristupili ovoj stranici direktno.",
        "resetpass-submit-loggedin": "Promijeni lozinku",
        "resetpass-submit-cancel": "Odustani",
        "passwordreset-emailtext-user": "Korisnik $1 na {{SITENAME}} je zatražio podsjetnik o detaljima Vašeg računa za {{SITENAME}} ($4). Sljedeći {{PLURAL:$3|korisnički račun je|korisnički računi su}} povezani s ovom e-mail adresom:\n\n$2\n\n{{PLURAL:$3|Ova privremena šifra|Ove privremene šifre}} će isteći za {{PLURAL:$5|jedan dan|$5 dana}}.\nTrebate se prijaviti i odabrati novu šifru. Ako je neko drugi napravio ovaj zahtjev, ili ako ste se sjetili Vaše originalne šifre, a ne želite je više promijeniti, možete zanemariti ovu poruku i nastaviti koristiti staru šifru.",
        "passwordreset-emailelement": "Korisničko ime: \n$1\n\nPrivremena šifra: \n$2",
        "passwordreset-emailsentemail": "Ako je ova adresa e-pošte povezana s Vašim računom, podsjetnik o lozinci bit će Vam poslan na adresu e-pošte.",
+       "passwordreset-nocaller": "Mora se navesti pozivalac",
+       "passwordreset-nosuchcaller": "Pozivalac ne postoji: $1",
+       "passwordreset-invalidemail": "Neispravna adresa e-pošte",
        "changeemail": "Promjena ili uklanjanje e-adrese",
        "changeemail-header": "Ispunite sljedeći formular da biste promijenili adresu e-pošte. Ako želite ukloniti postojeću adresu e-pošte s vašeg korisničkog računa, pri ispunjavanju formulara, polje nove adrese e-pošte ostavite prazno.",
        "changeemail-no-info": "Morate biti prijavljeni za direktan pristup ovoj stranici.",
        "copyrightwarning2": "Zapamtite da svi doprinosi na stranici {{SITENAME}} može biti izmijenjen, promijenjen ili uklonjen od strane ostalih korisnika. Ako ne želite da ovo desi sa Vašim tekstom, onda ga nemojte slati ovdje.<br />\nTakođer nam garantujete da ste ovo Vi napisali, ili da ste ga kopirali iz javne domene ili sličnog slobodnog izvora informacija (pogledajte $1 za više detalja).\n'''NE ŠALJITE AUTORSKIM PRAVOM ZAŠTIĆENE TEKSTOVE BEZ DOZVOLE!'''",
        "editpage-cannot-use-custom-model": "Model sadržaja ove stranice se ne može promijeniti.",
        "longpageerror": "'''Greška: Tekst, koji ste poslali, je dug {{PLURAL:$1|jedan kilobajt|$1 kilobajta}}, što je veće od maksimuma, koji iznosi {{PLURAL:$2|jedan kilobajt|$2 kilobajta}}.'''\nStranica ne može biti sačuvana.",
-       "readonlywarning": "'''PAŽNJA: Baza je zaključana zbog održavanja, tako da nećete moći da sačuvate svoje izmjene za sada.'''\nMožda želite da kopirate i nalijepite tekst u tekst editor i sačuvate ga za kasnije.\n\nAdministrator koji je zaključao bazu je naveo slijedeće objašnjenje: $1",
+       "readonlywarning": "<strong>Upozorenje: Baza je zaključana zbog održavanja, tako da nećete moći da sačuvate svoje izmjene za sada.</strong>\nMožda želite da kopirate i nalijepite tekst u tekst editor i sačuvate ga za kasnije.\n\nAdministrator koji je zaključao bazu je naveo sljedeće objašnjenje: $1",
        "protectedpagewarning": "'''PAŽNJA: Ova stranica je zaključana tako da samo korisnici sa administratorskim privilegijama mogu da je mijenjaju.'''\nPosljednja stavka u zapisniku je prikazana ispod kao referenca:",
        "semiprotectedpagewarning": "'''Pažnja:''' Ova stranica je zaključana tako da je samo registrovani korisnici mogu uređivati.\nPosljednja stavka zapisnika je prikazana ispod kao referenca:",
        "cascadeprotectedwarning": "<strong>Upozorenje:</strong> Ova stranica je zaključana tako da je samo administratori mogu mijenjati, jer je ona uključena u {{PLURAL:$1|ovu, lančanu povezanu, zaštićenu stranicu|sljedeće, prenosivo povezane, zaštićene stranice}}:",
        "invalid-content-data": "Nevaljani podaci sadržaja",
        "content-not-allowed-here": "Sadržaj napisan u obliku \"$1\" nije dozvoljen na stranici [[$2]]",
        "editwarning-warning": "Napuštanje ove stranice može dovesti do gubitka svih promjena koje ste načinili.\nAko ste prijavljeni, možete isključiti ovo upozorenje u Sekciji za \"{{int:prefs-editing}}\" vaših opcija.",
+       "editpage-invalidcontentmodel-title": "Model sadržaja nije podržan",
+       "editpage-invalidcontentmodel-text": "Model sadržaja \"$1\" nije podržan.",
        "editpage-notsupportedcontentformat-title": "Format sadržaja nije podržan",
        "editpage-notsupportedcontentformat-text": "Format sadržaja $1 nije podržan za model sadržaja $2.",
        "content-model-wikitext": "wikitekst",
        "content-model-css": "CSS",
        "content-json-empty-object": "Prazan objekat",
        "content-json-empty-array": "Prazan niz",
+       "deprecated-self-close-category": "Stranice s neispravnim samozatvorenim HTML oznakama",
        "duplicate-args-warning": "<strong>Upozorenje:</strong> [[:$1]] poziva na [[:$2]] sa više od jedne vrijednosti za parametar \"$3\". Koristit će se samo posljednja navedena vrijednost.",
        "duplicate-args-category": "Stranice sa istim argumentima kod poziva šablona",
        "duplicate-args-category-desc": "Stranica sadrži pozive šablona koji koriste argumente dvojnike, kao što su <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> ili <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "mergehistory-empty": "Nema revizija za spajanje.",
        "mergehistory-done": "$3 {{PLURAL:$3|izmjena|izmjene|izmjena}} stranice $1 uspješno je spojeno u [[:$2]].",
        "mergehistory-fail": "Ne može se izvršiti spajanje historije, molimo provjerite opet stranicu i parametre vremena.",
+       "mergehistory-fail-bad-timestamp": "Vremenska oznaka nije ispravna.",
+       "mergehistory-fail-invalid-source": "Izvorna stranica nije ispravna.",
+       "mergehistory-fail-invalid-dest": "Odredišna stranica nije ispravna.",
+       "mergehistory-fail-no-change": "Spajanje historije nije spojilo nijednu izmjenu. Provjerite parametre stranice i vremena.",
+       "mergehistory-fail-permission": "Nemate dopuštenje da spojite historiju.",
+       "mergehistory-fail-self-merge": "Izvorna i odredišna stranica su iste.",
        "mergehistory-fail-toobig": "Ne može se izvršiti spajanje historije jer će se više premjestiti više od ograničenja od $1 {{PLURAL:$1|revizije|revizija}}.",
        "mergehistory-no-source": "Izvorna stranica $1 ne postoji.",
        "mergehistory-no-destination": "Odredišna stranica $1 ne postoji.",
        "search-interwiki-caption": "Srodni projekti",
        "search-interwiki-default": "$1 rezultati:",
        "search-interwiki-more": "(više)",
+       "search-interwiki-more-results": "više rezultata",
        "search-relatedarticle": "Povezano",
        "searchrelated": "povezano",
        "searchall": "sve",
        "search-external": "Vanjska pretraga",
        "searchdisabled": "Pretraga na stranici {{SITENAME}} je onemogućena.\nU međuvremenu možete tražiti preko Googlea.\nUpamtite da ispisi stranice {{SITENAME}} mogu biti zastarjeli.",
        "search-error": "Desila se greška prilikom pretraživanja: $1",
+       "search-warning": "Došlo je do upozorenja prilikom pretraživanja: $1",
        "preferences": "Postavke",
        "mypreferences": "Postavke",
        "prefs-edits": "Broj izmjena:",
        "youremail": "Adresa e-pošte:",
        "username": "{{GENDER:$1|Korisničko}} ime:",
        "prefs-memberingroups": "{{GENDER:$2|Korisnik|Korisnica}} je član {{PLURAL:$1|grupe|grupâ}}:",
+       "group-membership-link-with-expiry": "$1 (do $2)",
        "prefs-registration": "Vrijeme registracije:",
        "yourrealname": "Vaše pravo ime:",
        "yourlanguage": "Jezik:",
        "userrights": "Postavke korisničkih prava",
        "userrights-lookup-user": "Izaberi korisnika",
        "userrights-user-editname": "Upišite korisničko ime:",
-       "editusergroup": "Uredi korisničke grupe",
-       "editinguser": "Mijenjate korisnička prava korisnika <strong>[[User:$1|$1]]</strong> $2",
-       "userrights-editusergroup": "Uredi korisničke grupe",
+       "editusergroup": "Učitaj korisničke grupe",
+       "editinguser": "Mijenjate korisnička prava {{GENDER:$1|korisnika|korisnice}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Pregledavate korisnička prava {{GENDER:$1|korisnika|korisnice}} <strong>[[User:$1|$1]]</strong> $2",
+       "userrights-editusergroup": "Uredi {{GENDER:$1|korisničke}} grupe",
+       "userrights-viewusergroup": "Vidi {{GENDER:$1|korisničke}} grupe",
        "saveusergroups": "Sačuvaj {{GENDER:$1|korisničke}} grupe",
        "userrights-groupsmember": "Član:",
        "userrights-groupsmember-auto": "Uključeni član od:",
-       "userrights-groups-help": "Možete promijeniti grupe kojima ovaj korisnik pripada:\n* Označeni kvadratić znači da je korisnik u toj grupi.\n* Neoznačen kvadratić znači da korisnik nije u toj grupi.\n* Oznaka * (zvjezdica) označava da Vi ne možete izbrisati ovu grupu ako je dodate i obrnutno.",
+       "userrights-groups-help": "Možete promijeniti grupe kojima ovaj korisnik pripada:\n* Označeni kvadratić znači da je korisnik u toj grupi.\n* Neoznačeni kvadratić znači da korisnik nije u toj grupi.\n* Zvjezdica (*) označava da ne možete ukloniti grupu nakon što je dodate i obrnuto.\n* Taraba (#) označava da jedino možete odložiti vrijeme isteka ove grupe; ne možete ga ubrzati.",
        "userrights-reason": "Razlog:",
        "userrights-no-interwiki": "Nemate dopuštenja da uređujete korisnička prava na drugim wikijima.",
        "userrights-nodatabase": "Baza podataka $1 ne postoji ili nije lokalna baza.",
        "userrights-changeable-col": "Grupe koje možete mijenjati",
        "userrights-unchangeable-col": "Grupe koje ne možete mijenjati",
+       "userrights-expiry-current": "Ističe $1",
+       "userrights-expiry-none": "Ne ističe",
+       "userrights-expiry": "Ističe:",
+       "userrights-expiry-existing": "Postojeće vrijeme isticanja: $3, $2",
+       "userrights-expiry-othertime": "Drugo vrijeme:",
+       "userrights-expiry-options": "1 dan:1 day,1 sedmica:1 week,1 mjesec:1 month,3 mjeseca:3 months,6 mjeseci:6 months,1 godina:1 year",
+       "userrights-invalid-expiry": "Vrijeme isticanja grupe \"$1\" nije ispravno.",
+       "userrights-expiry-in-past": "Vrijeme isticanja grupe \"$1\" je u prošlosti.",
+       "userrights-cannot-shorten-expiry": "Ne možete ubrzati vrijeme isteka grupe \"$1\". Jedino je mogu ubrzati korisnici koji mogu dodavati i uklanjati ovu grupu.",
        "userrights-conflict": "Sukob u izmjeni korisničkih prava! Molimo da razmotrite i potvrdite Vaše promjene.",
        "group": "Grupa:",
        "group-user": "Korisnici",
-       "group-autoconfirmed": "Potvrđeni korisnici",
+       "group-autoconfirmed": "Automatski potvrđeni korisnici",
        "group-bot": "Botovi",
        "group-sysop": "Administratori",
        "group-bureaucrat": "Birokrati",
        "group-suppress": "Skrivači",
        "group-all": "(sve)",
        "group-user-member": "{{GENDER:$1|korisnik|korisnica}}",
-       "group-autoconfirmed-member": "Potvrđeni korisnik",
-       "group-bot-member": "bot",
+       "group-autoconfirmed-member": "{{GENDER:$1|automatski potvrđen korisnik|automatski potvrđena korisnica}}",
+       "group-bot-member": "{{GENDER:$1|bot}}",
        "group-sysop-member": "{{GENDER:$1|administrator|administratorica}}",
        "group-bureaucrat-member": "{{GENDER:$1|birokrat|birokratica}}",
        "group-suppress-member": "{{GENDER:$1|skrivač|skrivačica}}",
        "grouppage-user": "{{ns:project}}:Korisnici",
-       "grouppage-autoconfirmed": "{{ns:project}}:Potvrđeni korisnici",
+       "grouppage-autoconfirmed": "{{ns:project}}:Automatski potvrđeni korisnici",
        "grouppage-bot": "{{ns:project}}:Botovi",
        "grouppage-sysop": "{{ns:project}}:Administratori",
        "grouppage-bureaucrat": "{{ns:project}}:Birokrati",
        "grouppage-suppress": "{{ns:project}}:Skrivač",
        "right-read": "Čitanje stranica",
        "right-edit": "Uređivanje stranica",
-       "right-createpage": "Pravljenje stranica (neuključujući stranice za razgovor)",
+       "right-createpage": "Pravljenje stranica (izuzev stranica za razgovor)",
        "right-createtalk": "Pravljenje stranica za razgovor",
-       "right-createaccount": "Pravljenje korisničkog računa",
-       "right-minoredit": "Označavanje izmjena kao malih",
+       "right-createaccount": "Pravljenje novih korisničkih računa",
+       "right-minoredit": "Označavanje izmjena manjim",
        "right-move": "Premještanje stranica",
-       "right-move-subpages": "Preusmjeravanje stranica sa svim podstranicama",
-       "right-move-rootuserpages": "Premještanje stranica osnovnih korisnika",
-       "right-move-categorypages": "Pomakni stranice kategorije",
+       "right-move-subpages": "Premještanje stranica s njihovim podstranicama",
+       "right-move-rootuserpages": "Premještanje osnovnih korisničkih stranica",
+       "right-move-categorypages": "Premještanje kategorija",
        "right-movefile": "Premještanje datoteka",
-       "right-suppressredirect": "Ne pravi preusmjeravanje sa starog imena pri preusmjeravanju stranica",
+       "right-suppressredirect": "Premještanje stranica bez ostavljanja preusmjerenja",
        "right-upload": "Postavljanje datoteka",
-       "right-reupload": "Postavljanje nove verzije datoteke",
-       "right-reupload-own": "Postavljanje nove verzije datoteke koju je postavio korisnik",
-       "right-reupload-shared": "Postavljanje novih lokalnih verzija datoteka identičnih onima u zajedničkoj ostavi",
+       "right-reupload": "Postavljanje novih verzija datoteka",
+       "right-reupload-own": "Postavljanje novih verzija vlastitih datoteka",
+       "right-reupload-shared": "Lokalno premošćivanje datoteka sa zajedničke ostave",
        "right-upload_by_url": "Postavljanje datoteke sa URL adrese",
-       "right-purge": "Osvježavanje keša za stranice bez konfirmacije",
-       "right-autoconfirmed": "Bez ograničavanja stavki za IP adrese",
+       "right-purge": "Osvježavanje keša stranice bez potvrde",
+       "right-autoconfirmed": "Izbjegavanje ograničenja stavki za IP adrese",
        "right-bot": "Postavljen kao automatski proces",
-       "right-nominornewtalk": "Male izmjene na stranici za razgovor ne uzrokuju prikazivanje oznake ''nova poruka'' na stranici za razgovor",
+       "right-nominornewtalk": "Izbjegavanje prikazivanja obavještenja o novim porukama kad je označeno da je izmjena manja",
        "right-apihighlimits": "Korištenje viših ograničenja u API upitima",
-       "right-writeapi": "Korištenje opcije ''write API''",
+       "right-writeapi": "Korištenje API-ja za pisanje",
        "right-delete": "Brisanje stranica",
        "right-bigdelete": "Brisanje stranica sa velikom historijom",
-       "right-deletelogentry": "Brisanje i vraćanje određenih zapisa u evidenciji",
-       "right-deleterevision": "Brisanje i vraćanje određenih revizija stranice",
-       "right-deletedhistory": "Pregled stavki obrisane historije, bez povezanog teksta",
-       "right-deletedtext": "Pregled obrisanog teksta i izmjena između obrisanih revizija",
+       "right-deletelogentry": "Brisanje i vraćanje određenih stavki u zapisniku",
+       "right-deleterevision": "Brisanje i vraćanje određenih izmjena stranice",
+       "right-deletedhistory": "Pregledanje stavki obrisane historije, bez povezanog teksta",
+       "right-deletedtext": "Pregledanje obrisanog teksta i izmjena između obrisanih izmjena",
        "right-browsearchive": "Pretraživanje obrisanih stranica",
        "right-undelete": "Vraćanje obrisanih stranica",
-       "right-suppressrevision": "Pregled, sakrivanje i povratak određenih revizija stranice od svih korisnika",
+       "right-suppressrevision": "Pregledanje, sakrivanje i vraćanje određenih verzija stranica od svih korisnika",
        "right-viewsuppressed": "Pregledaj izmjene skrivene od svih korisnika",
-       "right-suppressionlog": "Gledanje privatnih zapisa",
-       "right-block": "Blokiranje uređivanja drugih korisnika",
-       "right-blockemail": "Blokiranje korisnika da šalje e-mail",
-       "right-hideuser": "Blokiranje korisničkog imena, i njegovo sakrivanje od javnosti",
-       "right-ipblock-exempt": "Zaobilaženje IP blokada, autoblokada i blokada IP grupe",
-       "right-unblockself": "Deblokiraj samog sebe",
-       "right-protect": "Promjena nivoa zaštite i uređivanje kaskadno zaštićenih stranica",
-       "right-editprotected": "Uređivanje stranice zaštićenih kao \"{{int:protect-level-sysop}}\"",
-       "right-editsemiprotected": "Uređivanje stranica zaštićenih kao  \"{{int:protect-level-autoconfirmed}}\"",
+       "right-suppressionlog": "Pregledanje privatnih zapisnika",
+       "right-block": "Blokiranje mogućnosti uređivanja drugim korisnicima",
+       "right-blockemail": "Blokiranje korisnikove mogućnosti da šalje e-poštu",
+       "right-hideuser": "Blokiranje korisničkog imena i njegovo sakrivanje od javnosti",
+       "right-ipblock-exempt": "Zaobilaženje IP-blokada, autoblokada i blokada opsega",
+       "right-unblockself": "Deblokiranje samog sebe",
+       "right-protect": "Mijenjanje nivoâ zaštite i uređivanje stranica pod prenosivom zaštitom",
+       "right-editprotected": "Uređivanje stranica pod zaštitom \"{{int:protect-level-sysop}}\"",
+       "right-editsemiprotected": "Uređivanje stranica pod zaštitom \"{{int:protect-level-autoconfirmed}}\"",
        "right-editcontentmodel": "Uređivanje modela sadržaja stranice",
        "right-editinterface": "Uređivanje korisničkog interfejsa",
        "right-editusercssjs": "Uređivanje CSS i JS datoteka drugih korisnika",
-       "right-editusercss": "Uređivanje CSS datoteka drugih korisnika",
-       "right-edituserjs": "Uređivanje JS datoteka drugih korisnika",
-       "right-editmyusercss": "Uredite svoje vlastite korisničke CSS datoteke",
-       "right-editmyuserjs": "Uredite vlastite korisničke JavaScript datoteke",
-       "right-viewmywatchlist": "Pogledaj svoj spisak praćenih stranica",
-       "right-editmywatchlist": "Uredite vlastiti spisak praćenja. Važno je spomenuti da će neke radnje dodati stranice na spisak, čak i bez ovog prava.",
-       "right-viewmyprivateinfo": "Pogledajte Vaše privatne podatke (npr, adresa e-pošte, pravo ime)",
-       "right-editmyprivateinfo": "Uredite svoje privatne podatke (npr. adresa e-pošte, pravo ime)",
-       "right-editmyoptions": "Uredite svoje postavke",
+       "right-editusercss": "Uređivanje tuđih CSS datoteka",
+       "right-edituserjs": "Uređivanje tuđih JavaScript datoteka",
+       "right-editmyusercss": "Uređivanje vlastitih CSS datoteka",
+       "right-editmyuserjs": "Uređivanje vlastitih JavaScript datoteka",
+       "right-viewmywatchlist": "Pregledanje vlastitog spiska praćenja",
+       "right-editmywatchlist": "Uređivanje vlastitog spiska praćenja. Važno je spomenuti da će neke radnje dodati stranice na spisak, čak i bez ovog prava.",
+       "right-viewmyprivateinfo": "Pregledanje vlastitih ličnih podataka (npr, adresa e-pošte, pravo ime)",
+       "right-editmyprivateinfo": "Uređivanje vlastitih ličnih podataka (npr. adresa e-pošte, pravo ime)",
+       "right-editmyoptions": "Uređivanje vlastitih postavki",
        "right-rollback": "Brzo vraćanje izmjena posljednjeg korisnika koji je uređivao određenu stranicu",
        "right-markbotedits": "Označavanje vraćenih izmjena kao izmjene bota",
        "right-noratelimit": "Izbjegavanje ograničenja uzrokovanih brzinom",
-       "right-import": "Uvoz stranica iz drugih wikija",
-       "right-importupload": "Uvoz stranica putem postavljanja datoteke",
-       "right-patrol": "Označavanje izmjena drugih korisnika patroliranim",
-       "right-autopatrol": "Vlastite izmjene se automatski označavaju kao patrolirane",
+       "right-import": "Uvoženje stranica iz drugih wikija",
+       "right-importupload": "Uvoženje stranica putem postavljanja datoteke",
+       "right-patrol": "Označavanje tuđih izmjena patroliranim",
+       "right-autopatrol": "Automatsko označavanje vlastitih izmjena patroliranim",
        "right-patrolmarks": "Pregled oznaka patroliranja u spisku nedavnih izmjena",
-       "right-unwatchedpages": "Gledanje spiska nepraćenih stranica",
+       "right-unwatchedpages": "Pregledanje spiska nepraćenih stranica",
        "right-mergehistory": "Spajanje historije stranica",
        "right-userrights": "Uređivanje svih korisničkih prava",
        "right-userrights-interwiki": "Uređivanje korisničkih prava korisnika na drugim wikijima",
        "right-siteadmin": "Zaključavanje i otključavanje baze podataka",
        "right-override-export-depth": "Izvoz stranica uključujući povezane stranice do dubine od 5 linkova",
-       "right-sendemail": "Slanje e-maila drugim korisnicima",
-       "right-managechangetags": "Napravi i (de)aktiviraj [[Special:Tags|oznake]]",
-       "right-applychangetags": "Primijeni [[Special:Tags|oznake]] na nečije izmjene",
-       "right-changetags": "Dodavanje ili uklanjanje raznih [[Special:Tags|oznaka]] na pojedinačnim verzijama i unosima zapisnika",
+       "right-sendemail": "Slanje e-pošte drugim korisnicima",
+       "right-managechangetags": "Pravljenje i (de)aktiviranje [[Special:Tags|oznaka]]",
+       "right-applychangetags": "Primjenjivanje [[Special:Tags|oznaka]] na nečije izmjene",
+       "right-changetags": "Dodavanje ili uklanjanje raznih [[Special:Tags|oznaka]] na pojedinačnim verzijama i unosima u zapisnicima",
+       "right-deletechangetags": "Brisanje [[Special:Tags|oznaka]] iz baze podataka",
        "grant-group-page-interaction": "Upravljanje stranicama",
+       "grant-group-file-interaction": "Rad s medijskim datotekama",
        "grant-group-watchlist-interaction": "Upravljanje Vašim spiskom praćenja",
+       "grant-group-email": "Slanje e-pošte",
        "grant-group-high-volume": "Izvršavanje velikog broja radnji",
        "grant-group-customization": "Prilagodbe i postavke",
+       "grant-group-administration": "Izvršavanje administrativnih radnji",
+       "grant-group-private-information": "Pristupanje Vašim ličnim podacima",
        "grant-group-other": "Raznovrsno djelovanje",
+       "grant-blockusers": "Blokiranje i deblokiranje korisnika",
+       "grant-createaccount": "Stvaranje računa",
        "grant-createeditmovepage": "Pravljenje, uređivanje i premještanje stranica",
+       "grant-delete": "Brisanje stranica, izmjena i unosa u zapisnicima",
+       "grant-editinterface": "Uređivanje imenskog prostora MediaWiki i korisničkih CSS/JavaScript stranica",
        "grant-editmycssjs": "Uređivanje Vašeg korisničkog CSS-a ili JavaScripta",
        "grant-editmyoptions": "Uređivanje Vaših postavki",
        "grant-editmywatchlist": "Uređivanje Vašeg spiska praćenja",
        "grant-editpage": "Uređivanje postojećih stranica",
        "grant-editprotected": "Uređivanje zaštićenih stranica",
        "grant-highvolume": "Veliki broj izmjena",
+       "grant-oversight": "Skrivanje korisnika i izmjena",
        "grant-patrol": "Patroliranje izmjena stranica",
+       "grant-privateinfo": "Pristupanje ličnim podacima",
+       "grant-protect": "Dodavanje i uklanjanje zaštita sa stranica",
+       "grant-rollback": "Vraćanje izmjena",
+       "grant-sendemail": "Slanje e-pošte drugim korisnicima",
        "grant-uploadeditmovefile": "Postavljanje, zamjena i premještanje datoteka",
        "grant-uploadfile": "Postavljanje novih datoteka",
        "grant-basic": "Osnovna prava",
+       "grant-viewdeleted": "Pregledanje obrisanih datoteka i stranica",
        "grant-viewmywatchlist": "Pregled Vašeg spiska praćenja",
+       "grant-viewrestrictedlogs": "Pregledanje ograničenih unosa u zapisniku",
        "newuserlogpage": "Zapisnik novih korisnika",
        "newuserlogpagetext": "Ovo je zapisnik o registraciji novih korisnika.",
        "rightslog": "Zapisnik korisničkih prava",
        "rightslogtext": "Ovo je zapisnik promjena korisničkih prava.",
        "action-read": "čitate ovu stranicu",
-       "action-edit": "uređujete ovu stranicu",
+       "action-edit": "uredite ovu stranicu",
        "action-createpage": "napravite ovu stranicu",
-       "action-createtalk": "pravite stranice za razgovor",
+       "action-createtalk": "napravite ovu stranicu za razgovor",
        "action-createaccount": "napravite ovaj korisnički račun",
        "action-history": "gledate historiju ove stranice",
-       "action-minoredit": "da označite ovu izmjenu kao malu",
+       "action-minoredit": "označite ovu izmjenu manjom",
        "action-move": "premjestite ovu stranicu",
-       "action-move-subpages": "premjestite ovu stranicu, i njene podstranice",
-       "action-move-rootuserpages": "premjestite stranice osnovnog korisnika",
-       "action-move-categorypages": "pomakni stranice kategorije",
-       "action-movefile": "premjesti ovu datoteku",
-       "action-upload": "postavljate ovu datoteku",
-       "action-reupload": "stavite novu verziju postojeće datoteke",
-       "action-reupload-shared": "postavite ovu datoteku iz zajedničke ostave",
+       "action-move-subpages": "premjestite ovu stranicu i njene podstranice",
+       "action-move-rootuserpages": "premještate osnovne korisničke stranice",
+       "action-move-categorypages": "premještate kategorije",
+       "action-movefile": "premjestite ovu datoteku",
+       "action-upload": "postavite ovu datoteku",
+       "action-reupload": "postavite novu verziju postojeće datoteke",
+       "action-reupload-shared": "premostite ovu datoteku sa zajedničke ostave",
        "action-upload_by_url": "postavite ovu datoteku putem URL adrese",
-       "action-writeapi": "koristite ''write API'' opciju",
+       "action-writeapi": "koristite API za pisanje",
        "action-delete": "obrišete ovu stranicu",
-       "action-deleterevision": "obrišete ovu reviziju",
-       "action-deletedhistory": "gledate obrisanu historiju ove stranice",
+       "action-deleterevision": "brišete izmjene",
+       "action-deletelogentry": "brišete stavke u zapisniku",
+       "action-deletedhistory": "pregledate obrisanu historiju ove stranice",
+       "action-deletedtext": "pregledate obrisani tekst izmjene",
        "action-browsearchive": "pretražujete obrisane stranice",
-       "action-undelete": "vratite ovu stranicu",
-       "action-suppressrevision": "pregledate i vratite ovu skrivenu reviziju",
-       "action-suppressionlog": "vidite ovaj privatni zapis",
-       "action-block": "blokirate uređivanje ovog korisnika",
-       "action-protect": "promijeniti nivo zaštite za ovu stranicu",
-       "action-rollback": "brzo vraćanje izmjena posljednjeg korisnika koji je uređivao određenu stranicu",
-       "action-import": "uvozite stranice iz druge wiki",
-       "action-importupload": "uvoz stranica putem postavljanja datoteke",
-       "action-patrol": "označite izmjene drugih kao patrolirane",
-       "action-autopatrol": "da Vaše izmjene budu označene kao patrolirane",
+       "action-undelete": "vraćate stranice",
+       "action-suppressrevision": "pregledate i vraćate sakrivene izmjene",
+       "action-suppressionlog": "pregledate ovaj privatni zapisnik",
+       "action-block": "blokirate mogućnost uređivanja ovom korisniku",
+       "action-protect": "promijenite nivoe zaštite ove stranice",
+       "action-rollback": "brzo vratite izmjenu posljednjeg korisnika koji je uređivao određenu stranicu",
+       "action-import": "uvozite stranice iz drugih wikija",
+       "action-importupload": "uvozite stranice putem postavljanja datoteke",
+       "action-patrol": "označite tuđe izmjene patroliranim",
+       "action-autopatrol": "označite vlastite izmjene patroliranim",
        "action-unwatchedpages": "pregledate spisak nepraćenih stranica",
        "action-mergehistory": "spajate historiju ove stranice",
        "action-userrights": "uređujete sva korisnička prava",
        "action-userrights-interwiki": "uređujete korisnička prava korisnika na drugim wikijima",
        "action-siteadmin": "zaključavate ili otključavate bazu podataka",
-       "action-sendemail": "pošalji e-mail poruke",
-       "action-editmywatchlist": "uredite svoj spisak praćenih stranica",
-       "action-viewmywatchlist": "pogledajte svoj spisak praćenih stranica",
-       "action-viewmyprivateinfo": "pogledajte svoje privatne informacije",
-       "action-editmyprivateinfo": "uredite svoje privatne podatke",
-       "action-editcontentmodel": "uredi model sadržaja stranice",
+       "action-sendemail": "šaljete e-poštu",
+       "action-editmyoptions": "uređujete vlastite postavke",
+       "action-editmywatchlist": "uredite vlastiti spisak praćenja",
+       "action-viewmywatchlist": "pregledate vlastiti spisak praćenja",
+       "action-viewmyprivateinfo": "pregledate vlastite lične podatke",
+       "action-editmyprivateinfo": "uređujete vlastite lične podatke",
+       "action-editcontentmodel": "uređujete model sadržaja stranice",
        "action-managechangetags": "pravite i (de)aktivirate oznake",
-       "action-applychangetags": "dodate oznake uz ve izmjene",
+       "action-applychangetags": "dodate oznake uz vlastite izmjene",
        "action-changetags": "dodate ili uklonite razne oznake na pojedinačnim verzijama i unosima u zapisnicima",
+       "action-deletechangetags": "brišete oznake iz baze podataka",
+       "action-purge": "osvježite keš ove stranice",
        "nchanges": "$1 {{PLURAL:$1|promjena|promjene|promjena}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|izmjena od Vaše posljedne posjete}}",
        "enhancedrc-history": "historija",
        "recentchanges-legend-heading": "<strong>Legenda:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|spisak novih stranica]])",
        "recentchanges-submit": "Prikaži",
+       "rcfilters-activefilters": "Aktivni filteri",
+       "rcfilters-restore-default-filters": "Vrati predodređene filtere",
+       "rcfilters-clear-all-filters": "Ukloni sve filtere",
+       "rcfilters-invalid-filter": "Neispravan filter",
+       "rcfilters-empty-filter": "Nema aktivnih filtera. Svi doprinosi su prikazani.",
+       "rcfilters-filterlist-title": "Filteri",
+       "rcfilters-filterlist-noresults": "Nema filtera",
+       "rcfilters-filter-registered-label": "Registrirani",
+       "rcfilters-filter-registered-description": "Prijavljeni korisnici.",
+       "rcfilters-filter-unregistered-label": "Anonimni",
+       "rcfilters-filter-unregistered-description": "Neprijavljeni korisnici.",
+       "rcfilters-filtergroup-authorship": "Autorstvo izmjena",
+       "rcfilters-filter-editsbyself-label": "Vlastite izmjene",
+       "rcfilters-filter-editsbyself-description": "Izmjene koje ste Vi napravili.",
+       "rcfilters-filter-editsbyother-label": "Tuđe izmjene",
+       "rcfilters-filter-editsbyother-description": "Izmjene koje su napravili drugi korisnici (ne Vi).",
+       "rcfilters-filtergroup-userExpLevel": "Korisničko iskustvo (samo za registrirane)",
+       "rcfilters-filter-userExpLevel-newcomer-label": "Novajlije",
+       "rcfilters-filter-userExpLevel-newcomer-description": "Manje od 10 izmjena i 4 dana aktivnosti.",
+       "rcfilters-filter-userExpLevel-learner-label": "Učenici",
+       "rcfilters-filter-userExpLevel-learner-description": "Više dana aktivnosti i izmjena od \"novajlija\", ali manje od \"iskusnih korisnika\".",
+       "rcfilters-filter-userExpLevel-experienced-label": "Iskusni korisnici",
+       "rcfilters-filter-userExpLevel-experienced-description": "Preko 30 dana aktivnosti i 500 izmjena.",
+       "rcfilters-filtergroup-automated": "Automatski doprinosi",
+       "rcfilters-filter-bots-label": "Bot",
+       "rcfilters-filter-bots-description": "Izmjene napravljene automatiziranim alatima.",
+       "rcfilters-filter-humans-label": "Čovjek (ne bot)",
+       "rcfilters-filter-humans-description": "Izmjene koje su napravili ljudi.",
+       "rcfilters-filtergroup-significance": "Značaj",
+       "rcfilters-filter-minor-label": "Manje izmjene",
+       "rcfilters-filter-minor-description": "Izmjene koje je njihov autor označio manjim.",
+       "rcfilters-filter-major-label": "Obične izmjene",
+       "rcfilters-filter-major-description": "Izmjene koje nisu označene manjim.",
+       "rcfilters-filtergroup-changetype": "Vrsta izmjene",
+       "rcfilters-filter-pageedits-label": "Izmjene stranica",
+       "rcfilters-filter-pageedits-description": "Izmjene wiki sadržaja, rasprava, opisa kategorija....",
+       "rcfilters-filter-newpages-label": "Stvaranje stranica",
+       "rcfilters-filter-newpages-description": "Izmjene kojima se stvaraju nove stranice.",
+       "rcfilters-filter-categorization-label": "Izmjene kategorija",
+       "rcfilters-filter-categorization-description": "Izmjene kojima se dodavaju ili uklanjaju kategorije.",
+       "rcfilters-filter-logactions-label": "Zapisane radnje",
+       "rcfilters-filter-logactions-description": "Administrativne radnje, pravljenje računa, brisanje stranica, postavljenje datoteka....",
        "rcnotefrom": "Ispod {{PLURAL:$5|je izmjena|su izmjene}} od <strong>$3, $4</strong> (do <strong>$1</strong> prikazano).",
        "rclistfrom": "Prikaži nove izmjene počev od $3 u $2",
        "rcshowhideminor": "$1 manje izmjene",
        "recentchangeslinked-page": "Naslov stranice:",
        "recentchangeslinked-to": "Pokaži promjene stranica koji su povezane sa datom stranicom",
        "recentchanges-page-added-to-category": "[[:$1]] dodana je u kategoriju",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] i još [[Special:WhatLinksHere/$1|{{PLURAL:$2|jedna stranica|$2 stranice|$2 stranica}}]] su dodane u kategoriju",
+       "recentchanges-page-added-to-category-bundled": "Stranica [[:$1]] dodana je u kategoriju, [[Special:WhatLinksHere/$1|ovu stranicu sadrže druge stranice]]",
        "recentchanges-page-removed-from-category": "[[:$1]] je uklonjena iz kategorije",
-       "recentchanges-page-removed-from-category-bundled": "[[:$1]] i još {{PLURAL:$2|jedna stranica|$2 stranice|$2 stranica}} su uklonjene iz kategorije",
+       "recentchanges-page-removed-from-category-bundled": "Stranica [[:$1]] uklonjena je iz kategorije, [[Special:WhatLinksHere/$1|ovu stranicu sadrže druge stranice]]",
        "autochange-username": "Automatska promjena MediaWikija",
        "upload": "Postavi datoteku",
        "uploadbtn": "Postavi datoteku",
        "upload-too-many-redirects": "URL sadrži previše preusmjerenja",
        "upload-http-error": "Desila se HTTP greška: $1",
        "upload-copy-upload-invalid-domain": "Kopije postavljenih datoteka nisu dostupne sa ove domene.",
+       "upload-dialog-disabled": "Onemogućeno je postavljanje datoteka pomoću ovog dijaloga.",
        "upload-dialog-title": "Postavi datoteku",
        "upload-dialog-button-cancel": "Odustani",
+       "upload-dialog-button-back": "Nazad",
        "upload-dialog-button-done": "Gotovo",
        "upload-dialog-button-save": "Sačuvaj",
        "upload-dialog-button-upload": "Postavi",
        "filerevert-submit": "Vrati",
        "filerevert-success": "'''Datoteka [[Media:$1|$1]]''' je vraćena na [$4 verziju od $3, $2].",
        "filerevert-badversion": "Ne postoji ranija lokalna verzija ove datoteke sa navedenim vremenskim podacima.",
+       "filerevert-identical": "Trenutna verzija datoteke identična je izabranoj.",
        "filedelete": "Obriši $1",
        "filedelete-legend": "Obriši datoteku",
        "filedelete-intro": "Brišete datoteku '''[[Media:$1|$1]]''' zajedno sa svom njenom historijom.",
        "protectedpages-performer": "Zaštita korisnika",
        "protectedpages-params": "Parametri zaštite",
        "protectedpages-reason": "Razlog",
+       "protectedpages-submit": "Prikaži stranice",
        "protectedpages-unknown-timestamp": "Nepoznato",
        "protectedpages-unknown-performer": "Nepoznati korisnik",
        "protectedtitles": "Zaštićeni naslovi",
        "protectedtitles-summary": "Na ovoj stranici se nalazi spisak trenutno zaštićenih naslova. Za spisak trenutno zaštićenih stranica vidi [[{{#special:ProtectedPages}}|{{int:protectedpages}}]].",
        "protectedtitlesempty": "Nijedan naslov članka trenutno nije zaštićen ovim parametrima.",
+       "protectedtitles-submit": "Prikaži naslove",
        "listusers": "Spisak korisnika",
        "listusers-editsonly": "Pokaži samo korisnike koji su uređivali",
        "listusers-creationsort": "Sortiraj po datumu pravljenja",
        "querypage-disabled": "Ova posebna stranica je onemogućena jer smanjuje performanse.",
        "apihelp": "API pomoć",
        "apihelp-no-such-module": "Modul \"$1\" nije pronađen.",
+       "apisandbox-unfullscreen": "Prikaži stranicu",
+       "apisandbox-reset": "Očisti",
+       "apisandbox-retry": "Pokušaj ponovo",
+       "apisandbox-loading": "Učitavam podatke o API modulu \"$1\"...",
+       "apisandbox-examples": "Primjeri",
+       "apisandbox-dynamic-parameters": "Dodatni parametri",
+       "apisandbox-dynamic-parameters-add-label": "Dodaj parametar:",
+       "apisandbox-dynamic-parameters-add-placeholder": "Ime parametra",
+       "apisandbox-dynamic-error-exists": "Parametar pod nazivom \"$1\" već postoji.",
+       "apisandbox-deprecated-parameters": "Zastarjeli parametri",
+       "apisandbox-fetch-token": "Automatski ispuni žeton",
+       "apisandbox-submit-invalid-fields-title": "Neka polja nisu ispravna",
+       "apisandbox-submit-invalid-fields-message": "Ispravite naznačena polja i pokušajte ponovo.",
+       "apisandbox-results": "Rezultati",
+       "apisandbox-continue": "Nastavi",
+       "apisandbox-continue-clear": "Očisti",
+       "apisandbox-multivalue-all-values": "$1 (sve vrijednosti)",
        "booksources": "Književni izvori",
        "booksources-search-legend": "Traži književne izvore",
        "booksources-search": "Traži",
        "activeusers-count": "$1 {{PLURAL:$1|izmjena|izmjene|izmjena}} u {{PLURAL:$3|posljednji $3 dan|posljednja $3 dana|posljednjih $3 dana}}",
        "activeusers-from": "Prikaži korisnike koji počinju sa:",
        "activeusers-noresult": "Nije pronađen korisnik.",
+       "activeusers-submit": "Prikaži aktivne korisnike",
        "listgrouprights": "Prava korisničkih grupa",
        "listgrouprights-summary": "Slijedi spisak korisničkih grupa na ovoj wiki, s njihovim pripadajućim pravima pristupa.\nMoguće je da o svakoj grupi postoje [[{{MediaWiki:Listgrouprights-helppage}}|dodatne informacije]].",
        "listgrouprights-key": "Legenda:\n* <span class=\"listgrouprights-granted\">Dodano pravo</span>\n* <span class=\"listgrouprights-revoked\">Uklonjeno pravo</span>",
        "listgrouprights-rights": "Prava",
        "listgrouprights-helppage": "Help:Prava grupe",
        "listgrouprights-members": "(spisak članova)",
-       "listgrouprights-addgroup": "Mogu dodati {{PLURAL:$2|grupu|grupe}}: $1",
-       "listgrouprights-removegroup": "Mogu ukloniti {{PLURAL:$2|grupu|grupe}}: $1",
+       "listgrouprights-addgroup": "Dodavanje {{PLURAL:$2|sljedeće grupe|sljedećih grupa}}: $1",
+       "listgrouprights-removegroup": "Uklanjanje {{PLURAL:$2|sljedeće grupe|sljedećih grupa}}: $1",
        "listgrouprights-addgroup-all": "Može dodavati sve grupe",
        "listgrouprights-removegroup-all": "Može ukloniti sve grupe",
        "listgrouprights-addgroup-self": "Može dodati {{PLURAL:$2|grupu|grupe|grupa}} na svoj račun: $1",
        "listgrouprights-namespaceprotection-header": "Ograničenja imenskog prostora",
        "listgrouprights-namespaceprotection-namespace": "Imenski prostor",
        "listgrouprights-namespaceprotection-restrictedto": "Prava kojima se dozvoljava korisniku da uređuje",
+       "listgrants": "Dozvole",
        "listgrants-summary": "Ovo je spisak OAuth dozvola s odgovarajućim pravima uz svaku dozvolu s desne strane. Korisnici aplikacijama mogu odobriti da koriste njihov korisnički račun ali uz ograničena prava u zavisnosti od tog koju dozvolu im korisnik omogući. Međutim, aplikacija koja se koristi korisničkim računom ne može koristiti prava koja korisnik ne posjeduje. Moguće je da postoje [[{{MediaWiki:Listgrouprights-helppage}}|dodatne informacije]] o pojedinim pravima.",
+       "listgrants-grant": "Dozvola",
+       "listgrants-rights": "Prava",
        "trackingcategories": "Praćenje kategorija",
        "trackingcategories-summary": "Ova stranica prikazuje prateće kategorije koje MediaWiki softver automatski popunjava. Njihovi nazivi se mogu promijeniti izmjenom odgovarajućih sistemskih poruka u imenskom prostoru {{ns:8}}.",
        "trackingcategories-msg": "Praćenje kategorije",
        "emailccsubject": "Kopiraj Vašu poruku za $1: $2",
        "emailsent": "Poruka poslata",
        "emailsenttext": "Vaša poruka je poslata e-poštom.",
-       "emailuserfooter": "Ovu e-poruku {{GENDER:$1|poslao|poslala}} je $1 {{GENDER:$2|korisniku|korisnici}} $2 pomoću funkcije \"{{int:emailuser}}\" s projekta {{SITENAME}}.",
+       "emailuserfooter": "Ovu e-poruku {{GENDER:$1|poslao|poslala}} je $1 {{GENDER:$2|korisniku|korisnici}} $2 pomoću funkcije \"{{int:emailuser}}\" s {{GRAMMAR:genitiv|{{SITENAME}}}}. Ako {{GENDER:$2|odgovorite}} na ovu e-poruku, {{GENDER:$2|Vaša}} će se poruka direktno poslati {{GENDER:$1|originalnom pošiljaocu|originalnoj pošiljatejici}} i otkrit ćete {{GENDER:$1|mu|joj}} {{GENDER:$2|svoju}} adresu e-pošte.",
        "usermessage-summary": "Ostavljanje sistemske poruke.",
        "usermessage-editor": "Sistem za poruke",
        "watchlist": "Spisak praćenja",
        "wlshowhideanons": "anonimne korisnike",
        "wlshowhidepatr": "patrolirane izmjene",
        "wlshowhidemine": "moje izmjene",
+       "wlshowhidecategorization": "kategorizaciju stranica",
        "watchlist-options": "Opcije spiska praćenja",
        "watching": "Pratim...",
        "unwatching": "Ne pratim...",
        "delete-toobig": "Ova stranica ima veliku historiju promjena, preko $1 {{PLURAL:$1|revizije|revizija}}.\nBrisanje takvih stranica nije dopušteno da bi se spriječilo slučajno preopterećenje servera na kojem je {{SITENAME}}.",
        "delete-warning-toobig": "Ova stranica ima veliku historiju izmjena, preko $1 {{PLURAL:$1|izmjene|izmjena}}.\nNjeno brisanje može dovesti do opterećenja operacione baze na {{SITENAME}};\nnastavite s oprezom.",
        "deleteprotected": "Ne možete obrisati ovu stranicu jer je zaštićena.",
-       "deleting-backlinks-warning": "'''Upozorenje:''' [[Special:WhatLinksHere/{{FULLPAGENAME}}|Druge stranice]] sadrže link ili vode na stranicu koju brišete.",
+       "deleting-backlinks-warning": "<strong>Upozorenje:</strong> [[Special:WhatLinksHere/{{FULLPAGENAME}}|Druge stranice]] sadrže link ili vode na stranicu koju brišete.",
        "rollback": "Vrati izmjene",
        "rollbacklink": "vrati",
        "rollbacklinkcount": "vrati $1 {{PLURAL:$1|izmjenu|izmjene|izmjena}}",
        "rollbacklinkcount-morethan": "vrati više od $1 {{PLURAL:$1|izmjene|izmjene|izmjena}}",
        "rollbackfailed": "Neuspješno vraćanje",
+       "rollback-missingrevision": "Ne mogu učitati podatke o izmjeni.",
        "cantrollback": "Ne mogu vratiti izmjenu;\nposljednji autor je ujedno i jedini.",
        "alreadyrolled": "Ne može se vratiti posljednja izmjena [[:$1]] od korisnika [[User:$2|$2]] ([[User talk:$2|razgovor]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]); neko je već izmijenio ili vratio članak na prethodnu provjerenu verziju.\n\nPosljednju izmjenu napravio je korisnik [[User:$3|$3]] ([[User talk:$3|razgovor]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Sažetak izmjene je bio: <em>$1</em>.",
        "changecontentmodel-success-text": "Model sadržaja stranice [[:$1]] je promijenjen.",
        "changecontentmodel-cannot-convert": "Model sadržaja stranice [[:$1]] se ne može pretvoriti u vrstu $2.",
        "changecontentmodel-nodirectediting": "Model sadržaja $1 ne podržava izravno uređivanje",
+       "changecontentmodel-emptymodels-title": "Nema dostupnih modela sadržaja",
+       "changecontentmodel-emptymodels-text": "Model sadržaja stranice [[:$1]] ne može se pretvoriti ni u jednu drugu vrstu.",
        "log-name-contentmodel": "Zapisnik promjene modela sadržaja",
        "log-description-contentmodel": "Događaji koji su povezani s modelom sadržaja stranice",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|napravio|napravila}} je stranicu $3 s nestandardnim modelom sadržaja \"$5\"",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|promijenio|promijenila}} je model sadržaja stranice $3 iz \"$4\" u \"$5\"",
        "logentry-contentmodel-change-revertlink": "vrati",
        "logentry-contentmodel-change-revert": "vrati",
        "unprotectedarticle": "uklonjena zaštita sa stranice \"[[$1]]\"",
        "movedarticleprotection": "podešavanja zaštite premještena sa \"[[$2]]\" na \"[[$1]]\"",
        "protectedarticle-comment": "{{GENDER:$2|Zaštitio|Zaštitila}} je stranicu \"[[$1]]\"",
+       "modifiedarticleprotection-comment": "{{GENDER:$2|Promijenjen nivo zaštite}} za \"[[$1]]\"",
+       "unprotectedarticle-comment": "{{GENDER:$2|Uklonjena zaštita}} sa stranice \"[[$1]]\"",
        "protect-title": "Mijenjate nivo zašite za \"$1\"",
        "protect-title-notallowed": "Pregled stepena zaštite za \"$1\"",
        "prot_1movedto2": "članak [[$1]] premješten na [[$2]]",
        "whatlinkshere-hidelinks": "$1 linkove",
        "whatlinkshere-hideimages": "$1 linkova do datoteke",
        "whatlinkshere-filters": "Filteri",
+       "whatlinkshere-submit": "Idi",
        "autoblockid": "Automatska blokada #$1",
        "block": "Blokiraj korisnika",
        "unblock": "Odblokiraj korisnika",
        "ipb-unblock": "Deblokiraj korisničko ime ili IP adresu",
        "ipb-blocklist": "Vidi postojeće blokade",
        "ipb-blocklist-contribs": "Doprinosi za {{GENDER:$1|$1}}",
+       "ipb-blocklist-duration-left": "još $1",
        "unblockip": "Odblokiraj korisnika",
        "unblockiptext": "Upotrebite donji upitnik da bi ste vratili\npravo pisanja ranije blokiranoj IP adresi\nili korisničkom imenu.",
        "ipusubmit": "Ukloni ovu blokadu",
        "unblocklink": "deblokiraj",
        "change-blocklink": "promijeni blokadu",
        "contribslink": "doprinosi",
-       "emaillink": "pošalji e-mail",
+       "emaillink": "pošalji e-poruku",
        "autoblocker": "Automatski ste blokirani jer dijelite IP-adresu sa \"[[User:$1|$1]]\".\nRazlog za blokiranje korisnika $1 je ''$2''",
        "blocklogpage": "Zapisnik blokiranja",
        "blocklog-showlog": "Ovaj korisnik je ranije blokiran. Zapisnik blokiranja je prikazan ispod kao referenca:",
        "cant-move-to-user-page": "Nemate dopuštenje da premjestite stranicu na korisničku stranicu (osim na korisničku podstranicu).",
        "cant-move-category-page": "Nemate dopuštene da premještate stranice kategorija.",
        "cant-move-to-category-page": "Nemate dopuštenje da premjestite stranicu na stranicu kategorije.",
+       "cant-move-subpages": "Nemate dopuštenje da premještate podstranice.",
+       "namespace-nosubpages": "Imenski prostor \"$1\" ne dozvoljava podstranice.",
        "newtitle": "Novi naslov:",
        "move-watch": "Prati izvornu i odredišnu stranicu",
        "movepagebtn": "Premjesti stranicu",
        "movelogpagetext": "Ispod je spisak premještenih stranica.",
        "movesubpage": "{{PLURAL:$1|Podstranica|Podstranice}}",
        "movesubpagetext": "Ova stranica ima $1 {{PLURAL:$1|podstranicu|podstranice|podstranica}} prikazanih ispod.",
+       "movesubpagetalktext": "Odgovarajuća stranica za razgovor ima $1 {{PLURAL:$1|podstranicu|podstranice|podstranica}} prikazanih ispod.",
        "movenosubpage": "Ova stranica nema podstranica.",
        "movereason": "Razlog:",
        "revertmove": "vrati",
        "move-leave-redirect": "Ostavi preusmjerenje",
        "protectedpagemovewarning": "'''Upozorenje:''' Ova stranica je zaključana tako da je mogu premještati samo korisnici sa ovlastima administratora.\nPosljednja stavka zapisnika je prikazana ispod kao referenca:",
        "semiprotectedpagemovewarning": "'''Napomena:''' Ova stranica je zaključana tako da je mogu uređivati samo registrovani korisnici.\nPosljednja stavka zapisnika je prikazana ispod kao referenca:",
-       "move-over-sharedrepo": "== Datoteka postoji ==\n[[:$1]] postoji na dijeljenom repozitorijumu. Premještanje datoteke na ovaj naslov će prepisati dijeljenu datoteku.",
+       "move-over-sharedrepo": "[[:$1]] postoji na dijeljenom skladištu. Premještanje datoteke na ovaj naslov zamijenit će dijeljenu datoteku.",
        "file-exists-sharedrepo": "Ime datoteke koje ste odabrali je već korišteno u dijeljenom repozitorijumu.\nMolimo odaberite drugo ime.",
        "export": "Izvezite stranice",
        "exporttext": "Možete izvesti tekst i historiju jedne ili više stranica uklopljene u XML kod.\nOvo se može uvesti u drugi wiki koristeći MediaWiki preko [[Special:Import|stranice uvoza]].\n\nZa izvoz stranica unesite njihove naslove u polje ispod, jedan naslov po retku, i označite želite li trenutnu verziju zajedno sa svim ranijim, ili samo trenutnu verziju sa informacijom o zadnjoj promjeni.\n\nU drugom slučaju možete koristiti i vezu, npr. [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] za stranicu [[{{MediaWiki:Mainpage}}]].",
        "export-download": "Spremi kao datoteku",
        "export-templates": "Uključi šablone",
        "export-pagelinks": "Uključi povezane stranice do dubine od:",
+       "export-manual": "Ručno dodaj stranice:",
        "allmessages": "Sve sistemske poruke",
        "allmessagesname": "Naziv",
        "allmessagesdefault": "Uobičajeni tekst",
        "tooltip-ca-nstab-category": "Pogledajte stranicu kategorije",
        "tooltip-minoredit": "Označi kao manju izmjenu",
        "tooltip-save": "Sačuvaj moje izmjene",
+       "tooltip-publish": "Objavi svoje izmjene",
        "tooltip-preview": "Pregledajte svoje izmjene. Preporučeno pri bilo kakvom uređivanju!",
        "tooltip-diff": "Prikaži moje izmjene u tekstu",
        "tooltip-compareselectedversions": "Uporedi razliku između dvije izabrane verzije ove stranice",
        "pageinfo-length": "Dužina stranice (u bajtovima)",
        "pageinfo-article-id": "ID stranice",
        "pageinfo-language": "Jezik sadržaja stranice",
+       "pageinfo-language-change": "promijeni",
        "pageinfo-content-model": "Model sadržaj stranice",
+       "pageinfo-content-model-change": "promijeni",
        "pageinfo-robot-policy": "Indeksiranje od strane robota",
        "pageinfo-robot-index": "Dozvoljeno",
        "pageinfo-robot-noindex": "Nije dozvoljeno",
        "pageinfo-category-pages": "Broj stranica",
        "pageinfo-category-subcats": "Broj potkategorija",
        "pageinfo-category-files": "Broj datoteka",
+       "pageinfo-user-id": "Korisnički ID",
        "markaspatrolleddiff": "Označi kao patrolirano",
        "markaspatrolledtext": "Označi stranicu kao patroliranu",
        "markaspatrolledtext-file": "Označi ovu verziju datoteke kao patroliranu",
        "patrol-log-header": "Ovo je zapisnik patroliranih izmjena.",
        "log-show-hide-patrol": "$1 zapis patroliranja",
        "log-show-hide-tag": "$1 zapisnik oznaka",
+       "confirm-markpatrolled-button": "U redu",
+       "confirm-markpatrolled-top": "Označiti izmjenu $3 stranice $2 patroliranom?",
        "deletedrevision": "Obrisana stara revizija $1",
        "filedeleteerror-short": "Greška pri brisanju datoteke: $1",
        "filedeleteerror-long": "Desile su se greške pri brisanju datoteke:\n\n$1",
        "newimages-legend": "Filter",
        "newimages-label": "Ime datoteke (ili dio imena):",
        "newimages-showbots": "Pokaži datoteke koje su poslali botovi",
+       "newimages-hidepatrolled": "Sakrij patrolirana postavljanja",
        "noimages": "Nema ništa.",
+       "gallery-slideshow-toggle": "Uključi/isključi sličice",
        "ilsubmit": "Traži",
        "bydate": "po datumu",
        "sp-newimages-showfrom": "Prikaz novih datoteka počev od $2, $1",
        "scarytranscludefailed-httpstatus": "[Preuzimanje šablona nije uspjelo za $1: HTTP $2]",
        "scarytranscludetoolong": "[URL je predugačak]",
        "deletedwhileediting": "'''Upozorenje''': Ova stranica je obrisana prije nego što ste počeli uređivati!",
-       "confirmrecreate": "Korisnik [[User:$1|$1]] ([[User talk:$1|razgovor]]) obrisao je ovaj članak pošto ste počeli uređivanje s razlogom:\n: ''$2''\n\nMolimo Vas da potvrdite da stvarno želite ponovo napraviti ovaj članak.",
-       "confirmrecreate-noreason": "Korisnik [[User:$1|$1]] ([[User talk:$1|razgovor]]) obrisao je ovaj članak nakon što ste ga počeli uređivati. Molimo Vas da potvrdite da stvarno želite ponovo napraviti ovaj članak.",
+       "confirmrecreate": "{{GENDER:$1|Korisnik|Korisnica}} [[User:$1|$1]] ([[User talk:$1|razgovor]]) {{GENDER:$1|obrisao|obrisala}} je ovu stranicu nakon što ste je počeli uređivati iz sljedećeg razloga:\n: <em>$2</em>\n\nPotvrdite da stvarno želite ponovo napraviti ovu stranicu.",
+       "confirmrecreate-noreason": "{{GENDER:$1|Korisnik|Korisnica}} [[User:$1|$1]] ([[User talk:$1|razgovor]]) {{GENDER:$1|obrisao|obrisala}} je ovu stranicu nakon što ste je počeli uređivati. Potvrdite da stvarno želite ponovo napraviti ovu stranicu.",
        "recreate": "Ponovno napravi",
        "unit-pixel": "px",
        "confirm_purge_button": "U redu",
        "confirm-watch-top": "Dodajte ovu stranu na Vaš spisak praćenih članaka",
        "confirm-unwatch-button": "U redu",
        "confirm-unwatch-top": "Izbrišite ovu stranu sa Vašeg spiska praćenih članaka",
+       "confirm-rollback-button": "U redu",
+       "confirm-rollback-top": "Ukloniti izmjene na ovoj stranici?",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← prethodna stranica",
        "imgmultipagenext": "slijedeća stranica →",
        "hebrew-calendar-m12-gen": "Elul",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|razgovor]])",
        "timezone-utc": "KSV",
+       "timezone-local": "Lokalno",
        "duplicate-defaultsort": "Upozorenje: Postavljeni ključ sortiranja \"$2\" zamjenjuje raniji ključ \"$1\".",
        "duplicate-displaytitle": "<strong>Upozorenje:</strong> Prikazivani naslov \"$2\" poništava prethodni naslov \"$1\".",
+       "restricted-displaytitle": "<strong>Upozorenje:</strong> Naslov za prikaz \"$1\" zanemaren je jer ne odgovara stvarnom naslovu stranice.",
        "invalid-indicator-name": "<strong>Greška:</strong> <code>Imena</code> pokazatelja stanja strance ne smiju biti prazna.",
        "version": "Verzija",
        "version-extensions": "Instalirana proširenja",
        "version-libraries-license": "Licenca",
        "version-libraries-description": "Opis",
        "version-libraries-authors": "Autori",
-       "redirect": "Preusmjerenje na datoteku, korisnika, stranicu ili oznaku izmjene",
+       "redirect": "Preusmjerenje na datoteku, korisnika, stranicu, izmjenu ili zapisnik",
        "redirect-summary": "Ova posebna stranica preusmjerava na datoteku (datog imena), stranicu (date oznake izmjene) ili korisničku stranicu (datog numeričkog identifikatora korisnika). Oznake se koriste na sljedeći način: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]] ili [[{{#Special:Redirect}}/user/101]].",
        "redirect-submit": "Idi",
        "redirect-lookup": "Tip vrijednosti:",
        "tag-filter": "Filter [[Special:Tags|oznaka]]:",
        "tag-filter-submit": "Filter",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|oznaka|oznake}}]]: $2)",
+       "tag-mw-contentmodelchange": "promjena modela sadržaja",
        "tags-title": "Oznake",
        "tags-intro": "Ova stranica prikazuje spisak oznaka koje softver može staviti na svaku izmjenu i njihovo značenje.",
        "tags-tag": "Naziv oznake",
        "tags-deactivate": "dekativiraj",
        "tags-hitcount": "$1 {{PLURAL:$1|izmjena|izmjene|izmjena}}",
        "tags-manage-no-permission": "Nemate pravo upravljati promjenama oznaka.",
+       "tags-manage-blocked": "Ne možete mijenjati oznake za promjenu dok {{GENDER:$1|ste}} blokirani.",
        "tags-create-heading": "Napravi novu oznaku",
        "tags-create-explanation": "Predoređeno je da će korisnici i botovi moći koristiti novonapravljene oznake.",
        "tags-create-tag-name": "Naziv oznake:",
        "tags-delete-not-allowed": "Oznake koje su definirane proširenjem se ne mogu obrisati ukoliko to proširenje isključivo ne dopušta.",
        "tags-delete-not-found": "Oznaka \"$1\" ne postoji.",
        "tags-delete-too-many-uses": "Oznaka \"$1\" je primjenjena na više od {{PLURAL:$2|revizija|revizijǎ}}, što znači da se ne može obrisati.",
-       "tags-delete-warnings-after-delete": "Oznaka \"$1\" uspješno je obrisana. Međutim, došlo je do {{PLURAL:$2|sljedećeg upozorenja|sljedećih upozorenja}}:",
+       "tags-delete-warnings-after-delete": "Oznaka \"$1\" je obrisana. Međutim, došlo je do {{PLURAL:$2|sljedećeg upozorenja|sljedećih upozorenja}}:",
+       "tags-delete-no-permission": "Nemate dopuštenje da brišete oznake za promjenu.",
        "tags-activate-title": "Aktiviraj oznaku",
        "tags-activate-question": "Aktivirate oznaku \"$1\".",
        "tags-activate-reason": "Razlog:",
        "htmlform-cloner-create": "Dodaj još",
        "htmlform-cloner-delete": "Ukloni",
        "htmlform-cloner-required": "Potrebna je barem jedna vrijednost.",
+       "htmlform-date-placeholder": "GGGG-MM-DD",
+       "htmlform-time-placeholder": "HH:MM:SS",
+       "htmlform-datetime-placeholder": "GGGG-MM-DD HH:MM:SS",
+       "htmlform-date-invalid": "Ne mogu prepoznati uneseni format datuma. Koristite format GGGG-MM-DD.",
+       "htmlform-time-invalid": "Ne mogu prepoznati uneseni format vremena. Koristite format GGGG-MM-DD.",
+       "htmlform-datetime-invalid": "Ne mogu prepoznati uneseni format za datum i vrijeme. Koristite format GGGG-MM-DD HH:MM:SS.",
+       "htmlform-date-toolow": "Navedena vrijednost je prije najranijeg dopuštenog datuma – $1.",
+       "htmlform-date-toohigh": "Navedena vrijednost je poslije krajnjeg dopuštenog datuma – $1.",
+       "htmlform-time-toolow": "Navedena vrijednost je prije najranijeg dopuštenog vremena – $1.",
+       "htmlform-time-toohigh": "Navedena vrijednost je poslije krajnjeg dopuštenog vremena – $1.",
+       "htmlform-datetime-toolow": "Navedena vrijednost je prije najranijeg dopuštenog datuma i vremena – $1.",
+       "htmlform-datetime-toohigh": "Navedena vrijednost je poslije krajnjeg dopuštenog datuma i vremena – $1.",
        "htmlform-title-badnamespace": "[[:$1]] nije u imenskom prostoru \"{{ns:$2}}\".",
        "htmlform-title-not-creatable": "Stranica \"$1\" se ne može napraviti",
        "htmlform-title-not-exists": "$1 ne postoji.",
        "logentry-suppress-block": "$1 {{GENDER:$2|blokirao|blokirala}} je {{GENDER:$4|$3}} sa vremenom isticanja blokade od $5 $6",
        "logentry-suppress-reblock": "$1 {{GENDER:$2|promijenio|promijenila}} je postavke za blokiranje {{GENDER:$4|korisnika|korisnice}} {{GENDER:$4|$3}} u trajanju od $5 $6",
        "logentry-import-upload": "$1 {{GENDER:$2|uvezao|uvezla}} je $3 postavljanjem datoteke",
+       "logentry-import-upload-details": "$1 {{GENDER:$2|uvezao|uvezla}} je $3 postavljanjem datoteke ($4 {{PLURAL:$4|izmjena|izmjene|izmjena}})",
        "logentry-import-interwiki": "$1 {{GENDER:$2|uvezao|uvezla}} je $3 s drugog wikija",
+       "logentry-import-interwiki-details": "$1 {{GENDER:$2|uvezao|uvezla}} je $3 sa $5 ($4 {{PLURAL:$4|izmjena|izmjene|izmjena}})",
        "logentry-merge-merge": "$1 {{GENDER:$2|spojio|spojila}} je $3 u $4 (sve do izmjene $5)",
        "logentry-move-move": "$1 {{GENDER:$2|premjestio|premjestila}} je stranicu $3 na $4",
        "logentry-move-move-noredirect": "$1 {{GENDER:$2|premjestio|premjestila}} je stranicu $3 na $4 bez ostavljanja preusmjerenja",
        "logentry-protect-protect-cascade": "$1 {{GENDER:$2|zaštitio|zaštitila}} je $3 $4 [prenosiva zaštita]",
        "logentry-protect-modify": "$1 {{GENDER:$2|promijenio|promijenila}} je stepen zaštite za $3 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2|promijenio|promijenila}} je nivo zaštite za $3 $4 [prenosiva zaštita]",
-       "logentry-rights-rights": "$1 {{GENDER:$2|promijenio|promijenila}} je članstvo grupe za $3 iz $4 u $5",
+       "logentry-rights-rights": "$1 {{GENDER:$2|promijenio|promijenila}} je članstvo grupe {{GENDER:$6|korisnika|korisnice}} $3 iz $4 u $5",
        "logentry-rights-rights-legacy": "$1 je {{GENDER:$2|promijenio|promijenila|promijenio}} članstvo grupe za $3",
        "logentry-rights-autopromote": "$1 {{GENDER:$1|je automatski promijenjeno članstvo|su automatski promijenjena članstva}} iz $4 u $5",
        "logentry-upload-upload": "$1 {{GENDER:$2|postavio|postavila}} je $3",
        "logentry-tag-update-logentry": "$1 {{GENDER:$2|ažurirao|ažurirala}} je oznake na zapisničkom unosu $5 strane $3 ({{PLURAL:$7|dodano}} $6; {{PLURAL:$9|uklonjeno}} $8)",
        "rightsnone": "(nema)",
        "revdelete-summary": "sažetak",
+       "rightslogentry-temporary-group": "$1 (privremeno, do $2)",
        "feedback-adding": "Dodajem povratne informacije na stranicu...",
        "feedback-back": "Nazad",
        "feedback-bugcheck": "Odlično! Molimo provjerite da se ne radi o nekom [$1 poznatom \"bugu\"].",
        "api-error-emptypage": "Stvaranje novih praznih stranica nije dozvoljeno.",
        "api-error-publishfailed": "Unutrašnja greška: Server nije uspio objaviti privremenu datoteku.",
        "api-error-stashfailed": "Unutrašnja greška: server nije mogao da spremi privremenu datoteku.",
-       "api-error-unknown-warning": "Nepoznato upozorenje: $1",
+       "api-error-unknown-warning": "Nepoznato upozorenje: \"$1\".",
        "api-error-unknownerror": "Nepoznata greška: \"$1\"",
        "duration-seconds": "$1 {{PLURAL:$1|sekunda|sekunde}}",
        "duration-minutes": "$1 {{PLURAL:$1|minut|minute|minuta}}",
        "pagelang-language": "Jezik",
        "pagelang-use-default": "Koristi podrazumijevani jezik",
        "pagelang-select-lang": "Izaberi jezik",
+       "pagelang-reason": "Razlog",
+       "pagelang-submit": "Pošalji",
+       "pagelang-nonexistent-page": "Stranica $1 ne postoji.",
+       "pagelang-unchanged-language": "Stranica $1 već je postavljena na jezik $2.",
+       "pagelang-unchanged-language-default": "Stranica $1 već je postavljena na predodređeni jezik sadržaja ovog wikija.",
+       "pagelang-db-failed": "Baza podataka nije uspjela promijeniti jezik stranice.",
        "right-pagelang": "promjena jezika stranice",
        "action-pagelang": "promijenite jezik stranice",
        "log-name-pagelang": "Zapisnik promjene jezika",
        "mediastatistics": "Statistika datoteka",
        "mediastatistics-summary": "Statistike o tipovima poslanih datoteka. Ovdje su uračunate samo najnovije verzije datoteka. Stare ili obrisane verzije nisu uračunate.",
        "mediastatistics-nbytes": "{{PLURAL:$1|$1 bajt|$1 bajt|$1 bajtova}} ($2; $3%)",
+       "mediastatistics-bytespertype": "Ukupna veličina datoteke ovog odlomka: {{PLURAL:$1|$1 bajt|$1 bajta|$1 bajtova}} ($2; $3%).",
+       "mediastatistics-allbytes": "Ukupna veličina svih datoteka: {{PLURAL:$1|$1 bajt|$1 bajta|$1 bajtova}} ($2).",
        "mediastatistics-table-mimetype": "MIME tip",
        "mediastatistics-table-extensions": "Moguća proširenja",
        "mediastatistics-table-count": "Broj datoteka",
        "mediastatistics-header-text": "Tekstualno",
        "mediastatistics-header-executable": "Izvršni",
        "mediastatistics-header-archive": "Kompresovani formati",
+       "mediastatistics-header-total": "Sve datoteke",
        "json-warn-trailing-comma": "$1 {{PLURAL:$1|prateća tačka uklonjena je|pratećih tački uklonjeno je}} iz JSON-a",
        "json-error-unknown": "Dogodio se problem s JSON-om. Greška: $1",
        "json-error-depth": "Prekoračena je maksimalna dubina",
        "special-characters-group-arabicextended": "prošireni arapski",
        "special-characters-group-persian": "Perzijski",
        "special-characters-group-hebrew": "hebrejski",
-       "special-characters-group-bangla": "bangladeški",
+       "special-characters-group-bangla": "bengalski",
        "special-characters-group-tamil": "tamilski",
        "special-characters-group-telugu": "telugu",
        "special-characters-group-sinhala": "sinhalski",
-       "special-characters-group-gujarati": "gujarati",
+       "special-characters-group-gujarati": "guarati",
        "special-characters-group-devanagari": "devanagari",
        "special-characters-group-thai": "tajlandski",
        "special-characters-group-lao": "laoski",
        "special-characters-title-emdash": "duga crta",
        "special-characters-title-minus": "minus",
        "mw-widgets-dateinput-no-date": "Nikakav datum nije izabran",
+       "mw-widgets-mediasearch-noresults": "Nema rezultata.",
        "mw-widgets-titleinput-description-new-page": "stranica još ne postoji",
        "mw-widgets-titleinput-description-redirect": "preusmjerava na $1",
-       "randomrootpage": "Slučajna root stranica",
+       "mw-widgets-categoryselector-add-category-placeholder": "Dodaj kategoriju...",
+       "mw-widgets-usersmultiselect-placeholder": "Dodaj više...",
+       "sessionmanager-tie": "Ne možete istovremeno koristiti nekoliko vrsta provjera autentičnosti: $1.",
+       "randomrootpage": "Slučajna korijenska stranica",
        "log-action-filter-block": "Vrsta blokiranja:",
        "log-action-filter-contentmodel": "Vrsta izmjene modela sadržaja:",
        "log-action-filter-delete": "Vrsta brisanja:",
        "log-action-filter-patrol": "Vrsta patroliranja:",
        "log-action-filter-protect": "Vrsta zaštite:",
        "log-action-filter-rights": "Vrsta izmjene korisničkih prava:",
+       "log-action-filter-suppress": "Vrsta skrivanja:",
        "log-action-filter-upload": "Vrsta postavljanja:",
        "log-action-filter-all": "Sve",
        "log-action-filter-block-block": "Blokiranje",
        "log-action-filter-contentmodel-change": "Izmjena modela sadržaja",
        "log-action-filter-contentmodel-new": "Nova stranica s nestandardnim modelom sadržaja",
        "log-action-filter-delete-delete": "Brisanje stranice",
+       "log-action-filter-delete-delete_redir": "Prepisivanje preusmjerenja",
        "log-action-filter-delete-restore": "Vraćanje obrisane stranice",
        "log-action-filter-delete-event": "Brisanje unosa u zapisniku",
        "log-action-filter-delete-revision": "Brisanje izmjene",
        "log-action-filter-protect-move_prot": "Premještanje zaštite",
        "log-action-filter-rights-rights": "Ručna izmjena",
        "log-action-filter-rights-autopromote": "Automatska izmjena",
+       "log-action-filter-suppress-event": "Skrivanje unosa u zapisniku",
+       "log-action-filter-suppress-revision": "Skrivanje izmjene",
+       "log-action-filter-suppress-delete": "Skrivanje stranice",
+       "log-action-filter-suppress-block": "Skrivanje korisnika blokiranjem",
+       "log-action-filter-suppress-reblock": "Skrivanje korisnika ponovnim blokiranjem",
        "log-action-filter-upload-upload": "Nova datoteka",
-       "log-action-filter-upload-overwrite": "Izmjena postojeće datoteke"
+       "log-action-filter-upload-overwrite": "Izmjena postojeće datoteke",
+       "authmanager-create-disabled": "Onemogućeno pravljenje računa.",
+       "authmanager-create-from-login": "Popunite polja da biste napravili račun.",
+       "authmanager-create-not-in-progress": "Pravljenje računa nije u toku ili su podaci o sesiji izgubljeni. Počnite ispočetka.",
+       "authmanager-authplugin-setpass-failed-title": "Promjena lozinke nije uspjela",
+       "authmanager-authplugin-setpass-bad-domain": "Neispravna domena.",
+       "authmanager-autocreate-noperm": "Automatsko pravljenje računa nije dozvoljeno.",
+       "authmanager-autocreate-exception": "Automatsko pravljenje računa privremeno je onemogućeno zbog prijašnjih greški.",
+       "authmanager-userdoesnotexist": "Korisnički račun \"$1\" nije registriran.",
+       "authmanager-email-help": "Adresa e-pošte",
+       "authmanager-realname-label": "Pravo ime",
+       "authmanager-realname-help": "Korisnikovo pravo ime",
+       "authmanager-provider-temporarypassword": "Privremena lozinka",
+       "authprovider-resetpass-skip-label": "Preskoči",
+       "specialpage-securitylevel-not-allowed-title": "Nije dozvoljeno",
+       "specialpage-securitylevel-not-allowed": "Žao nam je, nije Vam dozvoljeno korištenje ove stranice jer ne možemo potvrditi Vaš identitet.",
+       "cannotauth-not-allowed-title": "Pristup odbijen",
+       "cannotauth-not-allowed": "Nije Vam dozvoljeno da koristite ovu stranicu",
+       "changecredentials": "Promjena akreditiva",
+       "changecredentials-submit": "Promijeni akreditive",
+       "changecredentials-invalidsubpage": "$1 nije ispravna vrsta akreditiva.",
+       "credentialsform-account": "Naziv računa:",
+       "cannotlink-no-provider-title": "Nema spojivih računa",
+       "cannotlink-no-provider": "Nema spojivih računa.",
+       "linkaccounts": "Spajanje računa",
+       "linkaccounts-success-text": "Račun je spojen.",
+       "linkaccounts-submit": "Spoji račune",
+       "unlinkaccounts": "Razdvajanje računa",
+       "unlinkaccounts-success": "Račun je razdvojen.",
+       "restrictionsfield-badip": "Neispravna IP-adresa ili opseg: $1",
+       "restrictionsfield-label": "Dozvoljeni IP-opsezi:",
+       "revid": "izmjena $1",
+       "pageid": "ID stranice $1"
 }
index 8171411..ce331c1 100644 (file)
        "move-page": "Reanomena $1",
        "move-page-legend": "Reanomena la pàgina",
        "movepagetext": "Amb el formulari següent reanomenareu una pàgina, movent tot el seu historial al nou nom.\nEl títol anterior es convertirà en una pàgina de redirecció al nou títol.\nPodeu actualitzar automàticament les redireccions que apuntin al títol original.\nSi no ho feu, assegureu-vos de verificar les redireccions [[Special:DoubleRedirects|dobles]] o [[Special:BrokenRedirects|trencades]].\nSerà de la vostra responsabilitat verificar que els enllaços segueixin apuntant cap a on se suposa que ho han de fer.\n\nTingueu en compte que la pàgina <strong>no</strong> serà traslladada si ja existeix una pàgina amb el títol nou, tret que sigui una redirecció sense més historial.\nAixò significa que podeu reanomenar de nou una pàgina al seu títol original si cometeu un error, i que no podeu sobreescriure una pàgina existent.\n\n<strong>Nota:</strong>\nAçò pot ser un canvi dràstic i inesperat en una pàgina que sigui popular; \nassegureu-vos d'entendre les conseqüències que comporta abans de seguir endavant.",
-       "movepagetext-noredirectfixer": "Amb el formulari següent podeu reanomenar una pàgina movent tot el seu historial al nom nou.\nEl títol anterior es convertirà en una pàgina de redirecció al nou títol. \nAssegureu-vos de verificar les redireccions [[Special:DoubleRedirects|dobles]] o [[Special:BrokenRedirects|trencades]].\nÉs responsabilitat vostra assegurar que els enllaços continuen apuntant cap a on se suposa que han d'anar. \n\nTingueu en compte que la pàgina <strong>no</strong> serà traslladada si ja existeix una pàgina amb el títol nou, tret que sigui una redirecció i no tingui més historial. \nAixò significa que podeu reanomenar de nou una pàgina al seu títol original si cometeu un error, i que no podeu sobreescriure una pàgina existent.\n \n<strong>Nota:</strong>\nAixò pot ser un canvi dràstic i inesperat per una pàgina popular; \nassegureu-vos que sabeu el que feu abans de continuar.",
+       "movepagetext-noredirectfixer": "Utilizatz lo formulari çaijós per renomenar una pagina, en desplaçant tot son istoric cap al nom novèl.\nL’ancian títol vendrà una pagina de redireccion cap al novèl títol.\nVerificatz plan las [[Special:DoubleRedirects|doblas redireccions]] o las [[Special:BrokenRedirects|redireccions copadas]].\nAvètz la responsabilitat de vos assegurar que los ligams contuhan de puntar cap a lor destinacion supausada.\n\nNotatz que la pagina serà <strong>pas</strong> desplaçada se existís ja una pagina amb lo títol novèl, levat se aquesta darrièra a un istoric de modificacions verge e es siá void, siá una simpla redireccion. Aquò permet de renomenar una pagina cap a sa posicion d’origina se lo desplaçament s’avèra erronèu, e es impossible d’espotir una pagina existenta.\n\n<strong>Atencion !</strong>\nAquò pòt provocar un cambiament radical e imprevist per una pagina sovent consultada ; asseguratz-vos de n'aver comprés las consequéncias abans de contunhar.",
        "movepagetalktext": "Si marqueu aquesta casella, la pàgina de discussió associada també serà traslladada automàticament al nou títol, tret que ja existeixi allà una pàgina de discussió no buida.\n\nEn aquest cas, haureu de traslladar o fusionar la pàgina manualment si ho desitgeu.",
        "moveuserpage-warning": "'''Atenció:''' Esteu a punt de moure una pàgina d'usuari. Tingueu en compte que només la pàgina es desplaçarà i que el compte d'usuari ''no'' canviarà de nom.",
        "movecategorypage-warning": "<strong>Avís:</strong> Esteu a punt de moure una pàgina de categoria. Tingueu en compte que només es mourà aquesta pàgina, i que les pàgines dins la categoria antiga <em>no</em> es recategoritzaran automàticament en la nova.",
index 0d9d219..2ae2a77 100644 (file)
@@ -18,7 +18,8 @@
                        "Macofe",
                        "Pirehelokan",
                        "Diyariq",
-                       "Sarchia"
+                       "Sarchia",
+                       "Lost Whispers"
                ]
        },
        "tog-underline": "ھێڵ ھێنان بەژێر بەستەرەکان:",
        "saveprefs": "پاشەکەوت",
        "restoreprefs": "ھەموو ڕێکخستنەکان ببەرەوە بۆ باری بنچینەیی (لە ھەموو بەشەکاندا)",
        "prefs-editing": "دەستکاریکردن",
-       "rows": "ڕیزەکان:",
-       "columns": "ستوونەکان:",
        "searchresultshead": "گەڕان",
        "stub-threshold": "سنوور بۆ شێوازی <a href=\"#\" class=\"stub\">بەستەری کۆڵکە</a> (بایت):",
        "stub-threshold-disabled": "ناچالاک",
        "tooltip-pt-mycontris": "پێڕستی بەشدارییەکان",
        "tooltip-pt-login": "پێشنیارت پێدەکرێ بچیتە ژوورەوە؛ ھەرچەندە زۆرت لێناکرێ",
        "tooltip-pt-logout": "دەرچوون",
+       "tooltip-pt-createaccount": "تۆ ھان دەدرێیت کە ھەژمارێک دروست بکەیت و بچیتە ژوورەوە، ئەمە سەرەکی نییە بەھەرحاڵ",
        "tooltip-ca-talk": "وتووێژ سەبارەت بە پەڕەی ناوەڕۆک",
        "tooltip-ca-edit": "دەستکاری ئەم پەڕەیە بکە‌",
        "tooltip-ca-addsection": "بەشێکی نوێ دەست پێ بکە",
        "feedback-submit": "ناردن",
        "searchsuggest-search": "لە {{SITENAME}}دا بگەڕێ",
        "searchsuggest-containing": "بە لەبەرگرتنەوەی ...",
-       "api-error-empty-file": "ئەو پەڕگەیە کە ناردووتە واڵا بوو.",
-       "api-error-file-too-large": "ئەو پەڕگەیە ناردووتە زۆر گەورەیە.",
-       "api-error-filename-tooshort": "ناوی پەڕگەکە زۆر کورتە.",
-       "api-error-filetype-banned": "ئەم جۆرە پەڕگەیە قەدەغەیە.",
-       "api-error-filetype-banned-type": "$1 {{PLURAL:$4|جۆرە پەڕگەیەکی ڕێگەپێدراو نییە|جۆرە پەڕگە ڕێگەپێدراوەکان نین}}. {{PLURAL:$3|جۆرە پەڕگەی ڕێگەپێدراو ئەمەیە|جۆرە پەڕگەکانی ڕێگەپێدراو ئەمانەن}}:  $2.",
-       "api-error-illegal-filename": "ناوی پەڕگە رێگەپێ‌نەدراوە.",
-       "api-error-unclassified": "ھەڵەیەکی نەزانراو ڕوویداوە.",
-       "api-error-unknown-code": "ھەڵەی نەزانراو: «$1».",
        "api-error-unknownerror": "ھەڵەی نەزانراو: «$1».",
        "duration-seconds": "$1 {{PLURAL:$1|چرکە|چرکە}}",
        "duration-minutes": "$1 {{PLURAL:$1|خولەک|خولەک}}",
index a768a12..bcc1a0f 100644 (file)
        "search-interwiki-caption": "Sesterské projekty",
        "search-interwiki-default": "Výsledky z $1:",
        "search-interwiki-more": "(více)",
+       "search-interwiki-more-results": "další výsledky",
        "search-relatedarticle": "Související",
        "searchrelated": "související",
        "searchall": "vše",
        "saveusergroups": "Uložit {{GENDER:$1|uživatelské}} skupiny",
        "userrights-groupsmember": "{{GENDER:$2|Člen|Členka}} {{PLURAL:$1|skupiny|skupin}}:",
        "userrights-groupsmember-auto": "Automaticky {{GENDER:$2|člen|členka}} {{PLURAL:$1|skupiny|skupin}}:",
-       "userrights-groups-help": "Můžete měnit skupiny, do nichž je {{GENDER:$1|uživatel zařazen|uživatelka zařazena}}.\n* Zaškrtnuté políčko znamená, že {{GENDER:$1|uživatel|uživatelka}} je v dané skupině.\n* Nezaškrtnuté políčko značí, že {{GENDER:$1|uživatel|uživatelka}} v dané skupině není.\n* Hvězdička (*) znamená, že nemůžete danou skupinu odstranit, jakmile ji přidáte, nebo naopak.\n* Křížek (#) znamená, že čas vypršení dané skupiny můžete pouze zkrátit, ne prodloužit.",
+       "userrights-groups-help": "Můžete měnit skupiny, do nichž je {{GENDER:$1|uživatel zařazen|uživatelka zařazena}}.\n* Zaškrtnuté políčko znamená, že {{GENDER:$1|uživatel|uživatelka}} je v dané skupině.\n* Nezaškrtnuté políčko značí, že {{GENDER:$1|uživatel|uživatelka}} v dané skupině není.\n* Hvězdička (*) znamená, že nemůžete danou skupinu odstranit, jakmile ji přidáte, nebo naopak.\n* Křížek (#) znamená, že čas do vypršení dané skupiny můžete pouze prodloužit, ne zkrátit.",
        "userrights-reason": "Důvod:",
        "userrights-no-interwiki": "Nemáte povoleno měnit uživatelská práva na jiných wiki.",
        "userrights-nodatabase": "Databáze $1 neexistuje nebo není místní.",
        "userrights-expiry-options": "1 den:1 day,1 týden:1 week,1 měsíc:1 month,3 měsíce:3 months,6 měsíců:6 months,1 rok:1 rok",
        "userrights-invalid-expiry": "Čas vypršení pro skupinu „$1“ je neplatný.",
        "userrights-expiry-in-past": "Čas vypršení pro skupinu „$1“ je v minulosti.",
-       "userrights-cannot-shorten-expiry": "Čas vypršení skupiny „$1“ nemůžete prodloužit. To mohou provést pouze uživatelé opravnění k jejímu přiřazení nebo odstranění.",
+       "userrights-cannot-shorten-expiry": "Čas do vypršení skupiny „$1“ nemůžete zkrátit. To mohou provést pouze uživatelé oprávnění k jejímu přiřazení nebo odstranění.",
        "userrights-conflict": "Konflikt změn uživatelských práv! Zkontrolujte a potvrďte požadované změny.",
        "group": "Skupina:",
        "group-user": "Uživatelé",
        "apisandbox-sending-request": "Odesílá se API požadavek…",
        "apisandbox-loading-results": "Přijímají se API výsledky…",
        "apisandbox-results-error": "Došlo k chybě při načítání odpovědi na API dotaz: $1.",
+       "apisandbox-request-selectformat-label": "Zobrazit data požadavku jako:",
+       "apisandbox-request-format-url-label": "řetězec dotazu do URL",
        "apisandbox-request-url-label": "URL požadavku:",
+       "apisandbox-request-json-label": "JSON požadavku:",
        "apisandbox-request-time": "Trvání požadavku: {{PLURAL:$1|$1 ms}}",
        "apisandbox-results-fixtoken": "Opravit token a znovu odeslat",
        "apisandbox-results-fixtoken-fail": "Nepodařilo se načíst token „$1“.",
        "mw-widgets-titleinput-description-new-page": "stránka zatím neexistuje",
        "mw-widgets-titleinput-description-redirect": "přesměrování na $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Přidat kategorii…",
+       "mw-widgets-usersmultiselect-placeholder": "Přidat další…",
        "sessionmanager-tie": "Nelze kombinovat několik typů autentizace požadavků: $1.",
        "sessionprovider-generic": "relace pomocí $1",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "relace pomocí cookies",
index 06f4da5..5ea1e92 100644 (file)
        "jumptosearch": "szëkbë",
        "aboutsite": "Ò {{SITENAME}}",
        "aboutpage": "Project:Ò_{{SITENAME}}",
-       "copyright": "Zamkłosc hewòtny starnë je ùżëczónô wedle reglów $1, jeżlë nie pòdóno jinaczi.",
+       "copyright": "Zamkłosc hewòtny starnë je ùprzëstãpnianô wedle reglów $1, jeżlë nie pòdóno jinaczi.",
        "copyrightpage": "{{ns:project}}:Ùsôdzkòwé_prawa",
        "currentevents": "Aktualné wëdarzenia",
        "currentevents-url": "Project:Aktualné wëdarzenia",
        "nstab-special": "Specjalnô starna",
        "nstab-project": "meta-starna",
        "nstab-image": "Òbrôzk",
-       "nstab-mediawiki": "Òłosënk",
+       "nstab-mediawiki": "Ògłosënk",
        "nstab-template": "Szablóna",
        "nstab-help": "Pòmòc",
        "nstab-category": "Kategòrëjô",
        "editinginterface": "'''ÒSTRZÉGA:''' Editëjesz starnã, jakô zamëkô w se tekst interfejsu softwôrë. Wszëtczé zmianë tu zrobioné bãdze widzec na interfejse jinszëch brëkòwników.\nPrzemëszlë dolmaczënié na [https://translatewiki.net/wiki/Main_Page?setlang=csb translatewiki.net], ekstra ùdbie lokalizacëji softwôrë MediaWiki.",
        "logouttext": "'''Jes wëlogòwóny.'''\nMòżesz robic dali na {{SITENAME}} jakno anonimòwi brëkòwnik abò sã <span class='plainlinks'>[$1 wlogòwac]</span> znowa jakno równy, a bò jinszi brëkòwnik.\nBôczë, że do czasu wëczëszczenia pòdrãczny pamiãcë przezérnika, niejedné starnë bãdą wëzdrzëc jakbë të bëł wlogòwóny.",
        "yourname": "Miono brëkòwnika",
+       "userlogin-yourname": "Pòzwa brëkòwnika",
        "yourpassword": "Twòja parola",
        "createacct-yourpassword-ph": "Wprowadzë hasło do przistãpù",
        "yourpasswordagain": "Pòwtórzë parolã",
        "createacct-yourpasswordagain": "Pòcwierdzë hasło",
        "createacct-yourpasswordagain-ph": "Wprowadzë hasło do przistãpù jesz rôz",
+       "userlogin-remembermypassword": "Nie wëlogòwùj mie",
        "yourdomainname": "Twòjô domena",
        "login": "Wlogùjë mie",
        "nav-login-createaccount": "Logòwanié",
        "logout": "Wëlogùj mie",
        "userlogout": "Wëlogòwanié",
        "notloggedin": "Felëje logòwóniô",
+       "userlogin-noaccount": "Ni môsz kònta?",
        "nologin": "Ni môsz kònta? '''$1'''.",
        "nologinlink": "Ùsadzë kònto",
        "createaccount": "Założë nowé kònto",
        "gotaccount": "Masz ju kònto? '''$1'''.",
        "gotaccountlink": "Wlogùjë",
+       "userlogin-resetpassword-link": "Zabôcził jes hasło?",
+       "userlogin-helplink2": "Pòmòc przë logòwaniu",
        "createacct-emailoptional": "Adres e-mail (òptacëjno)",
        "createacct-email-ph": "Pòdôj swój adres e-mail.",
        "createaccountmail": "Ùżij timczasowégò hasła i wësli je na pòdóny adres e-mail.",
        "retypenew": "Napiszë nową parolã jesz rôz",
        "resetpass-submit-loggedin": "Zmiana parolë",
        "resetpass-submit-cancel": "Anulujë",
+       "passwordreset-username": "Pòzwa brëkòwnika",
        "bold_sample": "Wëtłëszczony drëk",
        "bold_tip": "Wëtłëszczony drëk",
        "italic_sample": "Ùchëłi tekst",
        "preview": "Pòdzérk",
        "showpreview": "Wëskrzëni pòdzérk",
        "showdiff": "Wëskrzëni zmianë",
-       "anoneditwarning": "<strong>Bôczë:</strong> Të nie je wlogòwóny. Jeżlë wëkònôsz jakąs zmianã, twój adres IP mdze widoczny pùbliczno. Jeżlë <strong>[$1 wlogùjesz sã]</strong> abò <strong>[$2 ùsadzësz kònto]</strong>twòje zjinaczi òstóną przëpisóné do kònta, co wicy mającë kònto òtrzimôsz rozmajité ùdogòdnienia.",
+       "anoneditwarning": "<strong>Bôczë:</strong> Të nie jes wlogòwóny. Jeżlë wëkònôsz jakąs zmianã, twòja adresa IP mdze widocznô dlô wszëtczich. Jeżlë <strong>[$1 wlogùjesz sã]</strong> abò <strong>[$2 ùsadzysz kònto]</strong>twòje zjinaczi òstóną przëpisóné do kònta, co wicy mającë kònto òtrzimôsz rozmajité ùdogòdnienia.",
+       "anonpreviewwarning": "Të nie jes wlogòwóny. Jeżlë wprowadzysz jaczés zjinaczi, twòja adresa IP mdze ùmieszczónô w historie edicji starnë.",
        "summary-preview": "Pòdzérk òpisënka:",
        "blockedtitle": "Brëkòwnik je zascëgóny",
        "blockedtext": "'''Twòje kònto abò ë IP-adresa òstałë zablokòwóné.'''\n\nZablokòwôł je $1.\nPòdónô przëczëna to:''$2''.\n\n * Zôczątk blokadë: $8\n * Kùńc blokadë: $6\n * Cél blokadë: $7\n\n\nBë zgwësnic sprawã zablokòwaniô mòżesz skòntaktowac sã z $1 abò jińszim [[{{MediaWiki:Grouppage-sysop}}|administratorã]].\nBoczë, że të ni mòżesz stądka sélac e-mailów, jeżlë nié môsz jesz zaregisterowóné e-mailowé adresë w [[Special:Preferences|nastôwach]].\nTwòjô aktualnô adresa IP to $3, a zablokòwónô adresa ID to #$5.\nProszëmë pòdac wëższé pòdôłczi przë wszëtczich pëtaniach.",
        "clearyourcache": "'''Bôczë: Pò zapisanim, mòże bãdzesz mùszôł òminąc pamiãc przezérnika bë òbaczëc zmianë.'''\n'''Mozilla / Firefox / Safari:''' przëtrzëmôj ''Shift'' òbczas klëkaniô na ''Zladëjë znowa'', abò wcësni ''Ctrl-F5'' abò ''Ctrl-R'' (''Command-R'' na kòmpùtrach Mac);\n'''Konqueror:''': klëkni na knąpã ''Zladëjë znowa'', abò wcësni ''F5'';\n'''Opera:''' wëczëszczë pòdrãczną pamiãc w ''Tools→Preferences'';\n'''Internet Explorer:'''przëtrzëmôj ''Ctrl'' òbczas klëkaniô na ''Zladëjë znowa'', abò wcësni ''Ctrl-F5''.",
        "updated": "(Zaktualnioné)",
        "previewnote": "<strong>To je blós pòdzérk.</strong>\n Artikel jesz nie je zapisóny!",
+       "continue-editing": "Przeńdzë do pòla edicje.",
        "editing": "Edicëjô $1",
        "editingsection": "Edicëjô $1 (dzél)",
        "explainconflict": "Chtos sfórtowôł wprowadzëc swòją wersëjã artikla òbczôs Twòji edicëji.\nGórné pòle edicëji zamëkô w se tekst starnë aktualno zapisóny w pòdôwkòwi baze.\nTwòje zmianë są w dólnym pòlu edicëji.\nBë wprowadzëc swòje zmianë mùszisz zmòdifikòwac tekst z górnégò pòla.\n'''Blós''' tekst z górnégò pòla mdze zapisóny w baze czej wcësniesz \"{{int:savearticle}}\".",
        "protectedpages": "Zazychrowóné starnë",
        "listusers": "Lësta brëkòwników",
        "newpages": "Nowé starnë",
-       "newpages-username": "Miono brëkòwnika:",
+       "newpages-username": "Pòzwa brëkòwnika:",
        "ancientpages": "Nôstarszé starnë",
        "move": "Przeniesë",
        "movethispage": "Przeniesë",
        "emailuser": "Wëslë e-maila do negò brëkòwnika",
        "defemailsubject": "E-mail òd {{SITENAME}}",
        "noemailtitle": "Felënk email-adresë",
+       "emailusername": "Pòzwa brëkòwnika",
        "emailfrom": "Òd:",
        "emailto": "Do:",
        "emailsubject": "Téma:",
        "import": "Impòrtëjë starnë",
        "tooltip-pt-userpage": "{{GENDER:|Twòja}} starna brëkòwnika",
        "tooltip-pt-mytalk": "{{GENDER:|Twòja}} starna diskùsje",
+       "tooltip-pt-anontalk": "Diskùsjô brëkòwnika dlô ti adresë IP",
        "tooltip-pt-preferences": "{{GENDER:|Mòje}}nastôwë",
        "tooltip-pt-watchlist": "Lësta artiklów jaczé òbzérôsz za zmianama",
        "tooltip-pt-mycontris": "Lësta {{GENDER:|twòjich}} edicji",
+       "tooltip-pt-anoncontribs": "Lësta edicji, jaczé bëłë zrobióné spòd ti adresë IP.",
        "tooltip-pt-login": "Rôczimë do wlogòwaniô sã, nie je to równak mùszebné.",
        "tooltip-pt-logout": "Wëlogòwanié",
        "tooltip-pt-createaccount": "Zachãcëwómë do ùsadzeniô kònta i wlogòwaniô, chòc nie je to òbrzészk.",
index e666f8e..f1c54d5 100644 (file)
        "edit-no-change": "Vırnayışê şıma qebul nêbı, çunke nuşte de yew vırnayış nêvıraziyo.",
        "postedit-confirmation-created": "Pele vıraziye.",
        "postedit-confirmation-restored": "Pele anciya vıraziye.",
-       "postedit-confirmation-saved": "Vurnayışê to qeyd biyo.",
+       "postedit-confirmation-saved": "Vırnayışê to qeyd bi.",
        "edit-already-exists": "Pelo newe nêvıraziyeno.\nPel ca ra esto.",
        "defaultmessagetext": "Metnê mesacê hesabiyayey",
        "content-failed-to-parse": "Qandê madela $3 zereyê $1, $2 sero nêagozyayo",
        "viewpagelogs": "Qeydanê na perrer bımotne",
        "nohistory": "Verorê vurnayışanê na perer çıni yo.",
        "currentrev": "Çımraviyarnayışo rocane",
-       "currentrev-asof": "$1 ra tepeya çımraviyarnayışê cıyo peyên",
+       "currentrev-asof": "$1 ra tepeya çım ra viyarnayışê cı'yo peyên",
        "revisionasof": "Çımraviyarnayışê $1",
        "revision-info": "Vurnayışo ke $1 de terefê {{GENDER:$6|$2}}$7 ra biyo",
        "previousrevision": "← Çımraviyarnayışo kıhanêr",
        "rev-deleted-user-contribs": "[nameyê karberi ya zi adresa IPy esteriya - vurnayış iştırakan ra nımniyo]",
        "rev-deleted-text-permission": "Çımraviyarnayışê ena pele '''esteriyo'''.\nBeno ke [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} deletion log] de teferruat esto.",
        "rev-suppressed-text-permission": "Çımraviyarnayışê ena pele '''degusneyayo'''.\nŞıma be idarekerina xo ra şenê ey bıvênê; beno ke [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} degusnayış] de teferruat esto.",
-       "rev-deleted-text-unhide": "Çımra viyarnayışê ena pele '''besterêno'''.\nBeno ke [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} besternayış] de teferruat esto.\nŞıma be idarekerina xo ra şenê hewna [$1 nê çımra viyarnayışi bıvinê], eke wazenê dewam kerê.",
+       "rev-deleted-text-unhide": "Çım ra viyarnayışê enê riperri <strong>esteriya</strong>.\nTeferruati [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} qeydê esterıtışi] yenê diyene.\nŞıma hewna şenê [$1 nê vırnayışi bıvênê], eke wazenê dewam kerê.",
        "rev-suppressed-text-unhide": "Çımra viyarnayışê ena pele '''Degusneyayo'''.\nBeno ke [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} degustış] de teferruat esto.\nŞıma be idarekerina xo ra şenê hewna [$1 nê çımraviyarnayışi bıvênê], eke wazenê dewam kerê.",
        "rev-deleted-text-view": "Çımra viyarnayışê ena pele '''besternêno'''.\nŞıma be idarekerina xo ra şenê ey bıvênê; beno ke [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} besternayış] de teferruat esto.",
        "rev-suppressed-text-view": "Çımraviyarnayışê ena pele '''degusneyayo'''.\nŞıma be idarekerina xo ra şenê ey bıvênê; beno ke [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} degusnayış] de teferruat esto.",
        "grant-editprotected": "Srar bıyaye peran bıvurnê",
        "grant-highvolume": "Vengê berzi dayış",
        "grant-oversight": "Karberan u ploğyayê revizyona bınımn",
-       "grant-patrol": "Perer de vurnayışa çımra ravyarn",
+       "grant-patrol": "{{GENDER:$1|$1i çım ra viyarna ra:$1}}",
        "grant-protect": "Şeveknayış u wedarıtışê şeveknayışê pelan",
        "grant-sendemail": "Karberanê binan rê e-posta bırışê",
        "grant-uploadeditmovefile": "Dosyaya bar ke, bıvurnê  yana berê",
index f9da48f..e917c62 100644 (file)
        "filerenameerror": "No se ha podido renombrar el archivo «$1» a «$2».",
        "filedeleteerror": "No se ha podido borrar el archivo «$1».",
        "directorycreateerror": "No se ha podido crear el directorio «$1».",
-       "directoryreadonlyerror": "La carpeta «$1» es de solo lectura.",
-       "directorynotreadableerror": "La carpeta «$1» no tiene permisos de lectura.",
+       "directoryreadonlyerror": "El directorio «$1» es de solo lectura.",
+       "directorynotreadableerror": "El directorio «$1» no tiene permisos de lectura.",
        "filenotfound": "No se ha encontrado el archivo «$1».",
        "unexpected": "Valor inesperado: «$1»=«$2».",
        "formerror": "Error: no se ha podido enviar el formulario.",
index 0c47e68..824ec6f 100644 (file)
        "passwordreset-domain": "Domeen:",
        "passwordreset-email": "E-posti aadress:",
        "passwordreset-emailtitle": "{{GRAMMAR:genitive|{{SITENAME}}}} konto andmed",
-       "passwordreset-emailtext-ip": "Keegi, arvatavasti sina ise, IP-aadressilt $1 palus lähtestada sinu {{GRAMMAR:genitive|{{SITENAME}}}} ($4) parooli. Selle e-posti aadressiga on seotud {{PLURAL:$3|järgmine konto|järgmised kontod}}:\n\n$2\n\n{{PLURAL:$3|See ajutine parool aegub|Need ajutised paroolid aeguvad}} {{PLURAL:$5|ühe|$5}} päeva pärast.\nPeaksid nüüd sisse logima ja uue parooli valima. Kui selle palve esitas keegi teine või kui sulle meenus su parool ja sa ei soovi seda enam muuta, võid teadet eirata ja jätkata vana parooli kasutamist.",
+       "passwordreset-emailtext-ip": "Keegi IP-aadressilt $1, arvatavasti sina ise, palus lähtestada sinu {{GRAMMAR:genitive|{{SITENAME}}}} ($4) parooli. Selle e-posti aadressiga on seotud {{PLURAL:$3|järgmine konto|järgmised kontod}}:\n\n$2\n\n{{PLURAL:$3|See ajutine parool aegub|Need ajutised paroolid aeguvad}} {{PLURAL:$5|ühe|$5}} päeva pärast.\nPeaksid nüüd sisse logima ja uue parooli valima. Kui selle palve esitas keegi teine või kui sulle meenus su parool ja sa ei soovi seda enam muuta, võid teadet eirata ja jätkata vana parooli kasutamist.",
        "passwordreset-emailtext-user": "{{GRAMMAR:genitive|{{SITENAME}}}} kasutaja $1 palus lähtestada sinu {{GRAMMAR:genitive|{{SITENAME}}}} ($4) parooli. Selle e-posti aadressiga on seotud {{PLURAL:$3|järgmine konto|järgmised kontod}}:\n\n$2\n\n{{PLURAL:$3|See ajutine parool aegub|Need ajutised paroolid aeguvad}} {{PLURAL:$5|ühe|$5}} päeva pärast.\nPeaksid nüüd sisse logima ja uue parooli valima. Kui selle palve esitas keegi teine või kui sulle meenus su parool ja sa ei soovi seda enam muuta, võid teadet eirata ja jätkata vana parooli kasutamist.",
        "passwordreset-emailelement": "Kasutajanimi: \n$1\n\nAjutine parool: \n$2",
        "passwordreset-emailsentemail": "Kui oled sidunud konto selle e-posti aadressiga, siis saadetakse sulle parooli lähtestamise e-kiri.",
        "content-json-empty-object": "Tühi objekt",
        "content-json-empty-array": "Tühi massiiv",
        "deprecated-self-close-category": "Vigaste endassesuletud HTML-siltidega leheküljed",
+       "deprecated-self-close-category-desc": "Leheküljel on endassesuletud HTML-silte nagu <code>&lt;b/></code> või <code>&lt;span/></code>. Nende kuvamisviis viiakse peagi vastavusse HTML5 spetsifikatsiooniga. Seetõttu selliseid silte vikitekstis enam kasutama ei peaks.",
        "duplicate-args-warning": "<strong>Hoiatus:</strong> [[:$1]] kutsub malli [[:$2]] nii, et parameetrile \"$3\" vastab rohkem kui üks väärtus. Väärtustest kasutatakse ainult viimast.",
        "duplicate-args-category": "Leheküljed, kus mallikutses on topeltargument",
        "duplicate-args-category-desc": "Lehekülg sisaldab mallikutseid, kus mõnd argumenti on kasutatud mitu korda, näiteks <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> või <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
        "search-interwiki-caption": "Sõsarprojektid",
        "search-interwiki-default": "Tulemused asukohast $1:",
        "search-interwiki-more": "(veel)",
+       "search-interwiki-more-results": "veel tulemusi",
        "search-relatedarticle": "Seotud",
        "searchrelated": "seotud",
        "searchall": "kõik",
        "file-thumbnail-no": "Failinimi algab eesliitega <strong>$1</strong>.\nSee paistab vähendatud suurusega pilt (''pisipilt'') olevat.\nKui sul on ka selle pildi täislahutusega versioon, laadi palun hoopis see üles, vastasel korral muuda palun faili nime.",
        "fileexists-forbidden": "Sellise nimega fail on juba olemas, seda ei saa üle kirjutada.\nPalun pöörduge tagasi ja laadige fail üles mõne teise nime all. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Samanimeline fail on juba olemas jagatud meediavaramus.\nKui soovid siiski oma faili üles laadida, siis palun mine tagasi ja kasuta teist failinime.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-no-change": "Üleslaaditav fail on faili <strong>[[:$1]]</strong> praeguse versiooni üksühene duplikaat.",
+       "fileexists-duplicate-version": "Üleslaaditav fail on faili <strong>[[:$1]]</strong> {{PLURAL:$2|vanema versiooni|vanemate versioonide}} üksühene duplikaat.",
        "file-exists-duplicate": "See fail on {{PLURAL:$1|järgmise faili|järgmiste failide}} duplikaat:",
        "file-deleted-duplicate": "Selle failiga ([[:$1]]) identne fail on hiljuti kustutatud.\nVaata selle faili kustutamise ajalugu enne jätkamist.",
        "file-deleted-duplicate-notitle": "Selle failiga identne fail on varem kustutatud ja pealkiri on varjatud.\nEnne kui jätkad uuesti üleslaadimisega, peaksid paluma olukorda hinnata kellelgi, kes saab vaadata varjatud andmeid.",
        "uploaded-script-svg": "Üleslaaditud SVG-failist leiti skriptitav element \"$1\".",
        "uploaded-hostile-svg": "Üleslaaditud SVG-faili laadielemendist leiti ebaturvaline CSS.",
        "uploaded-event-handler-on-svg": "Sündmuse halduse atribuutide <code>$1=\"$2\"</code> seadmine pole SVG-failis lubatud.",
+       "uploaded-href-attribute-svg": "SVG-failis on lubatud href-atribuudiga viidata ainult sihtkohta skeemiga http:// või https://. Leiti <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-href-unsafe-target-svg": "Üleslaaditud SVG-failist leiti href, mis viitab ebaturvalistele andmetele: URI sihtkoht <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-animate-svg": "Üleslaaditud SVG-failist leiti silt \"animate\", mis võib href-i muuta, kasutades from-atribuuti <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "uploaded-setting-event-handler-svg": "Sündmuse halduse atribuutide seadmine on keelatud, üleslaaditud SVG-failist leiti <code>&lt;$1 $2=\"$3\"&gt;</code>.",
        "upload-too-many-redirects": "URL sisaldas liiga palju ümbersuunamisi",
        "upload-http-error": "HTTP-viga: $1",
        "upload-copy-upload-invalid-domain": "Sellest domeenist pole kopeerimise teel üleslaadimine võimalik.",
+       "upload-foreign-cant-upload": "Selles vikis pole häälestatud failide üleslaadimine päritud välisesse failihoidlasse.",
+       "upload-foreign-cant-load-config": "Ei õnnestunud laadida häälestust, mis puudutab failide üleslaadimist välisesse failihoidlasse.",
+       "upload-dialog-disabled": "Selle dialoogi kasutamine üleslaadimiseks on siin vikis keelatud.",
        "upload-dialog-title": "Faili üleslaadimine",
        "upload-dialog-button-cancel": "Loobu",
+       "upload-dialog-button-back": "Tagasi",
        "upload-dialog-button-done": "Valmis",
        "upload-dialog-button-save": "Salvesta",
        "upload-dialog-button-upload": "Laadi üles",
        "upload-form-label-infoform-title": "Üksikasjad",
        "upload-form-label-infoform-name": "Pealkiri",
+       "upload-form-label-infoform-name-tooltip": "Ainukordne ja kirjeldav pealkiri, millest saab failinimi. Võid kasutada lihtteksti ja tühikuid. Ära lisa nimele faililaiendit.",
        "upload-form-label-infoform-description": "Kirjeldus",
+       "upload-form-label-infoform-description-tooltip": "Anna teose kohta lühidalt edasi kõik märkimisväärne.\nFoto juures maini, mida on kujutatud, mis sündmuse või kohaga on tegu.",
        "upload-form-label-usage-title": "Kasutus",
        "upload-form-label-usage-filename": "Failinimi",
-       "upload-form-label-own-work": "See on minu enda töö",
+       "upload-form-label-own-work": "See on minu enda looming.",
        "upload-form-label-infoform-categories": "Kategooriad",
        "upload-form-label-infoform-date": "Kuupäev",
        "upload-form-label-own-work-message-generic-local": "Kinnitan, et seda faili üles laadides järgin saidi {{SITENAME}} kasutustingimusi ja litsentsipõhimõtteid.",
        "zip-wrong-format": "Valitud fail ei ole ZIP-fail.",
        "zip-bad": "See ZIP-fail on kas rikutud või muul põhjusel loetamatu.\nSelle turvalisust ei saa kontrollida.",
        "zip-unsupported": "See ZIP-fail kasutab ZIP-funktsioone, mida MediaWiki ei toeta.\nSelle turvalisust ei saa kontrollida.",
-       "uploadstash": "Üleslaaditud failide algne hoidla",
+       "uploadstash": "Üleslaadimise peithoidla",
        "uploadstash-summary": "See lehekülg pakub juurdepääsu failidele, mis on üles laaditud (või mida parasjagu üles laaditakse), kuid mis pole veel vikis avaldatud. Need failid on nähtavad üksnes kasutajale, kes need üles laadis.",
-       "uploadstash-clear": "Kustuta failid algsest hoidlast",
-       "uploadstash-nofiles": "Sul pole algses hoidlas faile.",
+       "uploadstash-clear": "Eemalda peitfailid",
+       "uploadstash-nofiles": "Sul pole peitfaile.",
        "uploadstash-badtoken": "Toiming ebaõnnestus, võib-olla redigeerimismandaadi aegumise tõttu. Palun proovi uuesti.",
-       "uploadstash-errclear": "Failide kustutamine ebaõnnestus.",
+       "uploadstash-errclear": "Failide eemaldamine ebaõnnestus.",
        "uploadstash-refresh": "Värskenda faililoendit",
+       "uploadstash-thumbnail": "vaata pisipilti",
+       "uploadstash-exception": "Üleslaaditavat faili ei õnnestunud peithoidlas talletada ($1): \"$2\".",
        "invalid-chunk-offset": "Tüki vigane nihe",
        "img-auth-accessdenied": "Juurdepääs keelatud",
        "img-auth-nopathinfo": "PATH_INFO puudub.\nSinu server pole seadistatud seda teavet edastama.\nSee võib olla CGI-põhine ja ei toeta img_auth-i.\nVaata lehekülge https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "filerevert-submit": "Taasta",
        "filerevert-success": "Faili '''[[Media:$1|$1]]''' seisuga [$4 $3, $2 kasutusel olnud versioon] on taastatud.",
        "filerevert-badversion": "Ette antud ajatempliga kohalik versioon sellest failist puudub.",
+       "filerevert-identical": "Praegune versioon on valitud versiooniga juba identne.",
        "filedelete": "Kustuta $1",
        "filedelete-legend": "Faili kustutamine",
        "filedelete-intro": "Oled kustutamas faili '''[[Media:$1|$1]]''' ja kogu selle ajalugu.",
        "uncategorizedcategories": "Kategoriseerimata kategooriad",
        "uncategorizedimages": "Kategoriseerimata failid",
        "uncategorizedtemplates": "Kategoriseerimata mallid",
+       "uncategorized-categories-exceptionlist": " # Loetelu kategooriatest, mis ei peaks kajastuma leheküljel \"Eri:Kategoriseerimata kategooriad\". Üks kategooria rea kohta, rea alguses \"*\". Muu märgiga (sh tühemik) algavaid ridu eiratakse. Komentaari jaoks kasuta märki \"#\".",
        "unusedcategories": "Kasutamata kategooriad",
        "unusedimages": "Kasutamata failid",
        "wantedcategories": "Kõige oodatumad kategooriad",
        "trackingcategories-name": "Sõnumi nimi",
        "trackingcategories-desc": "Kategooriasse arvamise kriteeriumid",
        "restricted-displaytitle-ignored": "Eiratava kuvapealkirjaga leheküljed",
+       "restricted-displaytitle-ignored-desc": "Leheküljel on <code><nowiki>{{DISPLAYTITLE}}</nowiki></code>, mida eiratakse, sest see ei vasta lehekülje tegelikule pealkirjale.",
        "noindex-category-desc": "Robotid ei indekseeri lehekülge, sest sellel on võlusõna <code><nowiki>__NOINDEX__</nowiki></code> ja lehekülg on nimeruumis, kus see silt on lubatud.",
        "index-category-desc": "Leheküljel on <code><nowiki>__INDEX__</nowiki></code> ja lehekülg on nimeruumis, kus see silt on lubatud ning seetõttu indekseerivad robotid lehekülge seal, kus nad muidu seda ei teeks.",
        "post-expand-template-inclusion-category-desc": "Kõigi mallide hõrendamise järel on lehekülg suurem kui <code>$wgMaxArticleSize</code>, mistõttu jäid mõned mallid hõrendamata.",
        "rollbacklinkcount": "tühista {{PLURAL:$1|üks muudatus|$1 muudatust}}",
        "rollbacklinkcount-morethan": "tühista üle {{PLURAL:$1|ühe muudatuse|10 muudatuse}}",
        "rollbackfailed": "Muudatuste tühistamine ebaõnnestus",
+       "rollback-missingparam": "Päringus puuduvad nõutavad parameetrid.",
+       "rollback-missingrevision": "Redaktsiooni andmeid ei õnnestu laadida.",
        "cantrollback": "Ei saa muudatusi eemaldada, sest viimane kaastööline on artikli ainus autor.",
        "alreadyrolled": "Muudatust, mille tegi lehele [[:$1]] kasutaja [[User:$2|$2]] ([[User talk:$2|arutelu]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]), ei saa tühistada, sest keegi teine on seda lehte vahepeal muutnud.\n\nLehte muutis viimasena [[User:$3|$3]] ([[User talk:$3|arutelu]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "Redaktsiooni resümee oli: <em>$1</em>.",
        "revertpage": "Tühistati kasutaja [[Special:Contributions/$2|$2]] ([[User talk:$2|arutelu]]) tehtud muudatused ja pöörduti tagasi viimasele muudatusele, mille tegi [[User:$1|$1]].",
        "revertpage-nouser": "Tühistati peidetud kasutaja muudatused ja pöörduti tagasi viimasele muudatusele, mille tegi [[User:$1|$1]].",
        "rollback-success": "Tühistati muudatused, mille tegi $1;\npöörduti tagasi viimasele muudatusele, mille tegi $2.",
+       "rollback-success-notify": "Tühistatud kasutaja $1 tehtud muudatused;\npöördutud tagasi kasutaja $2 viimase redaktsiooni juurde. [$3 Näita muudatusi]",
        "sessionfailure-title": "Seansiviga",
        "sessionfailure": "Sinu sisselogimisseansiga näib probleem olevat.\nSee toiming on seansiärandamise vastase ettevaatusabinõuna tühistatud.\nMine tagasi eelmisele leheküljele ja taaslaadi see, seejärel proovi uuesti.",
        "changecontentmodel": "Lehekülje sisumudeli muutmine",
        "changecontentmodel-success-text": "Lehekülje [[:$1]] sisumudel on muudetud.",
        "changecontentmodel-cannot-convert": "Lehekülje [[:$1]] sisumudelit ei saa teisendada tüübiks $2.",
        "changecontentmodel-nodirectediting": "Sisumudel $1 ei võimalda otseredigeerimist.",
+       "changecontentmodel-emptymodels-title": "Sisumudeleid pole saadaval",
+       "changecontentmodel-emptymodels-text": "Lehekülje [[:$1]] sisu ei saa teisendada ühtegi tüüpi mudelisse.",
        "log-name-contentmodel": "Sisumudeli muutmislogi",
        "log-description-contentmodel": "Siin on loetletud lehekülgede sisumudelite muudatused ning leheküljed, mis loodi vaikeväärtusest erineva sisumudeliga.",
+       "logentry-contentmodel-new": "$1 {{GENDER:$2|lõi}} lehekülje $3 vaikeväärtusest erineva sisumudeliga \"$5\"",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|muutis}} lehekülje \"$3\" sisumudeli: \"$4\" → \"$5\"",
        "logentry-contentmodel-change-revertlink": "võta tagasi",
        "logentry-contentmodel-change-revert": "tagasi võetud",
        "proxyblockreason": "Sinu IP-aadress on blokeeritud, sest see on avatud proksi. Palun võta ühendust oma internetiteenuse pakkujaga või tehnilise toega ja teata neile sellest probleemist.",
        "sorbsreason": "Sinu IP-aadress on {{GRAMMAR:genitive|{{SITENAME}}}} kasutatavas DNS-põhises mustas nimekirjas märgitud kui avatud proksi.",
        "sorbs_create_account_reason": "Sinu IP-aadress on {{GRAMMAR:genitive|{{SITENAME}}}} kasutatavas DNS-põhises mustas nimekirjas märgitud kui avatud proksi.\nSa ei saa kasutajakontot luua.",
+       "softblockrangesreason": "Sinu IP-aadressilt ($1) pole anonüümne kaastöö lubatud. Palun logi sisse.",
        "xffblockreason": "X-Forwarded-Fori päises esinev IP-aadress, mis kuulub kas sulle või proksiserverile, mida kasutad, on blokeeritud. Blokeerimise algne põhjus oli: $1",
        "cant-see-hidden-user": "Kasutaja, keda blokeerida üritad, on juba blokeeritud ning peidetud. Kuna sul pole õigust blokeerida kasutajanimesid, peites need avalikkuse eest, ei saa sa selle kasutaja blokeeringut vaadata ega muuta.",
        "ipbblocked": "Sa ei saa teisi blokeerida ega nende blokeeringuid eemaldada, sest oled ise blokeeritud.",
        "lockdbsuccesstext": "Andmebaas on nüüd lukustatud.<br />\nKui sinu hooldustöö on läbi, ära unusta [[Special:UnlockDB|kirjutuspääsu taastada]]!",
        "unlockdbsuccesstext": "Andmebaasi kirjutuspääs on taastatud.",
        "lockfilenotwritable": "Andmebaasi lukufail ei ole kirjutatav.\nAndmebaasi lukustamiseks ja avamiseks peavad veebiserveril olema sellele kirjutusõigused.",
+       "databaselocked": "Andmebaas on juba lukustatud.",
        "databasenotlocked": "Andmebaas ei ole lukustatud.",
        "lockedbyandtime": "(lukustas $1; $2, kell $3)",
        "move-page": "Lehekülje \"$1\" teisaldamine",
        "cant-move-to-user-page": "Sul ei ole õigust teisaldada lehekülge kasutajaleheks (ei käi kasutaja alamlehe kohta).",
        "cant-move-category-page": "Sul pole õigust kategoorialehekülgi teisaldada.",
        "cant-move-to-category-page": "Sul pole õigust teisaldada lehekülge kategoorialeheküljele.",
+       "cant-move-subpages": "Sul pole lubatud alamlehekülgi teisaldada.",
+       "namespace-nosubpages": "Nimeruumis \"$1\" pole alamleheküljed lubatud.",
        "newtitle": "Uus pealkiri:",
        "move-watch": "Jälgi lähte- ja sihtlehekülge",
        "movepagebtn": "Teisalda lehekülg",
        "movelogpagetext": "See logi sisaldab infot lehekülgede teisaldamistest.",
        "movesubpage": "{{PLURAL:$1|Alamlehekülg|Alamleheküljed}}",
        "movesubpagetext": "Selle lehekülje $1 {{PLURAL:$1|alamlehekülg|alamlehekülge}} on kuvatud allpool.",
+       "movesubpagetalktext": "Seonduval aruteluleheküljel on $1 allnäidatud {{PLURAL:$1|alamlehekülg|alamlehekülge}}.",
        "movenosubpage": "Sellel leheküljel pole alamlehekülgi.",
        "movereason": "Põhjus:",
        "revertmove": "taasta",
        "pageinfo-length": "Lehekülje pikkus (baitides)",
        "pageinfo-article-id": "Lehekülje identifikaator",
        "pageinfo-language": "Lehekülje sisu keel",
+       "pageinfo-language-change": "muuda",
        "pageinfo-content-model": "Lehekülje sisumudel",
        "pageinfo-content-model-change": "muuda",
        "pageinfo-robot-policy": "Robotindekseering",
        "log-show-hide-patrol": "$1 kontrollimislogi",
        "log-show-hide-tag": "$1 märgiste logi",
        "confirm-markpatrolled-button": "Sobib",
+       "confirm-markpatrolled-top": "Kas märgid lehekülje $2 redaktsiooni $3 kontrollituks?",
        "deletedrevision": "Kustutatud vanem versioon $1",
        "filedeleteerror-short": "Tõrge faili kustutamisel: $1",
        "filedeleteerror-long": "Faili kustutamisel esines tõrkeid:\n\n$1",
        "exif-photometricinterpretation-0": "Mustvalge (valge on 0)",
        "exif-photometricinterpretation-1": "Mustvalge (must on 0)",
        "exif-photometricinterpretation-3": "Palett",
+       "exif-photometricinterpretation-4": "Läbipaistvusmask",
        "exif-photometricinterpretation-5": "Eraldatud (arvatavasti CMYK)",
        "exif-photometricinterpretation-9": "CIE L*a*b* (ICC kodeering)",
        "exif-photometricinterpretation-10": "CIE L*a*b* (ITU kodeering)",
        "confirmemail_body_set": "Keegi IP-aadressilt $1, arvatavasti sina ise, on {{GRAMMAR:genitive|{{SITENAME}}}} konto \"$2\" e-posti aadressiks määranud selle aadressi.\n\nKinnitamaks, et see konto kuulub tõesti sulle ja et aktiveerida e-posti teenused, ava võrgulehitsejas järgmine link:\n\n$3\n\nKui konto *ei* kuulu sulle, kasuta e-posti aadressi kinnituse tühistamiseks järgmist linki:\n\n$5\n\nSelle kinnituskoodi aegumistähtaeg on $4.",
        "confirmemail_invalidated": "E-posti aadressi kinnitamine tühistati",
        "invalidateemail": "E-posti aadressi kinnituse tühistamine",
+       "notificationemail_subject_changed": "{{GRAMMAR:inessive|{{SITENAME}}}} registreeritud e-posti aadress on muudetud",
+       "notificationemail_subject_removed": "{{GRAMMAR:inessive|{{SITENAME}}}} registreeritud e-posti aadress on eemaldatud",
+       "notificationemail_body_changed": "Keegi IP-aadressilt $1, arvatavasti sina ise,\nmuutis {{GRAMMAR:inessive|{{SITENAME}}}} konto \"$2\" e-posti aadressiks \"$3\".\n\nKui see ei olnud sina, siis võta viivitamata ühendust saidi administraatoriga.",
+       "notificationemail_body_removed": "Keegi IP-aadressilt $1, arvatavasti sina ise,\neemaldas {{GRAMMAR:inessive|{{SITENAME}}}} konto \"$2\" e-posti aadressi.\n\nKui see ei olnud sina, siis võta viivitamata ühendust saidi administraatoriga.",
        "scarytranscludedisabled": "[Vikidevaheline mallina kasutamine on keelatud]",
        "scarytranscludefailed": "[Malli $1 hankimine ebaõnnestus]",
        "scarytranscludefailed-httpstatus": "[Malli $1 hankimine ebaõnnestus: HTTP $2]",
        "confirm-unwatch-button": "Sobib",
        "confirm-unwatch-top": "Kas eemaldad selle lehekülje oma jälgimisloendist?",
        "confirm-rollback-button": "Sobib",
+       "confirm-rollback-top": "Kas tühistad sellel leheküljel tehtud muudatused?",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← eelmine lehekülg",
        "imgmultipagenext": "järgmine lehekülg →",
        "watchlisttools-edit": "vaata ja redigeeri jälgimisloendit",
        "watchlisttools-raw": "redigeeri jälgimisloendi toorandmeid",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|arutelu]])",
+       "timezone-local": "Kohalik",
        "duplicate-defaultsort": "'''Hoiatus:''' Järjestamisvõti \"$2\" tühistab eespool oleva järjestamisvõtme \"$1\".",
        "duplicate-displaytitle": "<strong>Hoiatus:</strong> Kuvatava pealkirjaga \"$2\" kirjutatakse üle varasem kuvatav pealkiri \"$1\".",
+       "restricted-displaytitle": "<strong>Hoiatus:</strong> Kuvapealkirja \"$1\" eirati, sest see ei vasta lehekülje tegelikule pealkirjale.",
        "invalid-indicator-name": "<strong>Tõrge:</strong> Lehekülje olekunäidu juures ei tohi atribuudi <code>name</code> väärtus puududa.",
        "version": "Versioon",
        "version-extensions": "Paigaldatud lisad",
        "redirect-page": "Lehekülje identifikaator",
        "redirect-revision": "Lehekülje redaktsioon",
        "redirect-file": "Failinimi",
+       "redirect-logid": "Logi identifikaator",
        "redirect-not-exists": "Väärtust ei leitud",
        "fileduplicatesearch": "Faili duplikaatide otsimine",
        "fileduplicatesearch-summary": "Otsi duplikaatfaile nende räsiväärtuse järgi.",
        "tags-delete-not-found": "Märgist \"$1\" pole.",
        "tags-delete-too-many-uses": "Märgist \"$1\" on rakendatud rohkem kui {{PLURAL:$2|ühe|$2}} redaktsiooni juures, mistõttu ei saa seda kustutada.",
        "tags-delete-warnings-after-delete": "Märgis \"$1\" on kustutatud, kuid väljastati {{PLURAL:$2|järgmine hoiatus|järgmised hoiatused}}:",
+       "tags-delete-no-permission": "Sul pole lubatud muudatusmärgiseid kustutada.",
        "tags-activate-title": "Märgise lubamine",
        "tags-activate-question": "Siinkohal lubad märgise \"$1\".",
        "tags-activate-reason": "Põhjus:",
        "htmlform-cloner-create": "Lisa veel",
        "htmlform-cloner-delete": "Eemalda",
        "htmlform-cloner-required": "Vähemalt üks väärtus on nõutav.",
+       "htmlform-date-placeholder": "AAAA-KK-PP",
+       "htmlform-time-placeholder": "TT:MM:SS",
+       "htmlform-datetime-placeholder": "AAAA-KK-PP TT:MM:SS",
        "htmlform-title-badnamespace": "[[:$1]] pole nimeruumis \"{{ns:$2}}\".",
        "htmlform-title-not-creatable": "Pealkirja \"$1\" all ei saa lehekülge alustada.",
        "htmlform-title-not-exists": "Lehekülge $1 pole olemas.",
index d1218e1..f9eb200 100644 (file)
        "userrights-user-editname": "Erabiltzaile izena idatzi:",
        "editusergroup": "Erabiltzaile taldeak kargatu",
        "editinguser": "<strong>[[User:$1|$1]]</strong> $2 {{GENDER:$1|lankidearen}} erabiltzaile-eskubideak aldatzen",
-       "userrights-editusergroup": "Erabiltzaile taldeak editatu",
+       "userrights-editusergroup": "{{GENDER:$1|Erabiltzaile}} taldeak editatu",
        "saveusergroups": "Erabiltzaile {{GENDER:$1|taldeak}} gorde",
        "userrights-groupsmember": "Ondorengo talde honetako kide da:",
        "userrights-groupsmember-auto": "Honen kide inplizitua:",
-       "userrights-groups-help": "Lankide hau zein taldetakoa den alda dezakezu:\n* Laukia hautatuta baldin badago, esan nahi du lankidea talde horretakoa dela.\n* Laukia hautatu gabe baldin badago, esan nahi du lankidea talde horretakoa ez dela.\n* Izartxoak (*) erakusten du ezin duzula talde horretatik kendu, taldera gehitu eta gero; edo alderantziz, ezin duzula talde horretara gehitu, taldetik kendu eta gero.",
+       "userrights-groups-help": "Lankide hau zein taldetakoa den alda dezakezu:\n* Laukia hautatuta baldin badago, esan nahi du lankidea talde horretakoa dela.\n* Laukia hautatu gabe baldin badago, esan nahi du lankidea talde horretakoa ez dela.\n* Izartxoak (*) erakusten du ezin duzula talde horretatik kendu, taldera gehitu eta gero; edo alderantziz, ezin duzula talde horretara gehitu, taldetik kendu eta gero.\n* Traolak (#) erakusten du taldearen iraungipen data luzatu egin dezakezula soilik; ez ordea aurreratu.",
        "userrights-reason": "Arrazoia:",
        "userrights-no-interwiki": "Ez duzu beste wikietan erabiltzaile eskumenak aldatzeko baimenik.",
        "userrights-nodatabase": "$1 datubasea ez da existitzen edo ez dago lokalki.",
        "rcfilters-filtergroup-authorship": "Edizioaren egiletza",
        "rcfilters-filter-bots-label": "Bot",
        "rcfilters-filter-minor-label": "Aldaketa txikiak",
-       "rcnotefrom": "Jarraian azaltzen diren aldaketak data honetatik aurrerakoak dira: <b>$2</b> (gehienez <b>$1</b> erakusten dira).",
+       "rcnotefrom": "Jarraian azaltzen diren {{PLURAL:$5|aldaketak}} data honetatik aurrerakoak dira: <strong>$3,$4</strong> (gehienez <b>$1</b> erakusten dira).",
        "rclistfrom": "Erakutsi $3 $2 ondorengo aldaketa berriak",
        "rcshowhideminor": "$1 aldaketa txikiak",
        "rcshowhideminor-show": "Erakutsi",
        "emailccsubject": "Zure mezuaren kopia $1(r)i: $2",
        "emailsent": "Mezua bidali egin da",
        "emailsenttext": "Zure e-posta mezua bidali egin da.",
-       "emailuserfooter": "E-posta hau $1(e)k {{GENDER:$1|bidali}} dio {{GENDER:$2|$2}}(r)i {{SITENAME}}ko \"{{int:emailuser}}\" funtzioa erabiliz.",
+       "emailuserfooter": "E-posta hau $1(e)k {{GENDER:$1|bidali}} dio {{GENDER:$2|$2}}(r)i {{SITENAME}}ko \"{{int:emailuser}}\" funtzioa erabiliz. Email honi erantzuten {{GENDER:$2|badiozu}}, zure emaila zuzenean jatorrizko {{GENDER:$1|igorleari}} bidaliko zaio eta {{GENDER:$1|harentzat}} {{GENDER:$2|zure}} email helbidea ikusgai geratuko da.",
        "usermessage-summary": "Sistema mezua uzten.",
        "usermessage-editor": "Sistemako mezularia",
        "watchlist": "Jarraipen zerrenda",
        "addedwatchtext": "\"[[:$1]]\" eta haren eztabaida orria zure [[Special:Watchlist|jarraipen zerrendara]] erantsi da. \n\nOrri honetan aurrerantzean egindako aldaketak zerrenda horretan agertuko dira.",
        "addedwatchtext-short": "$1 orria zure jarraipen zerrendara gehitu da.",
        "removewatch": "Kendu zure jarraipen zerrendatik",
-       "removedwatchtext": "\"[[:$1]]\" orrialdea zure [[Special:Watchlist|jarraipen zerrendatik]] kendu da.",
+       "removedwatchtext": "\"[[:$1]]\" eta haren eztabaida orrialdea zure [[Special:Watchlist|jarraipen zerrendatik]] kendu da.",
        "removedwatchtext-short": "$1 orria zure jarraipen zerrendatik ezabatu da.",
        "watch": "Jarraitu",
        "watchthispage": "Orrialde hau jarraitu",
index 0d1ec60..c5b45d9 100644 (file)
        "recentchanges-legend-plusminus": "(''±123'')",
        "recentchanges-submit": "Näytä",
        "rcfilters-activefilters": "Aktiiviset suodattimet",
+       "rcfilters-clear-all-filters": "Tyhjennä kaikki suodattimet",
+       "rcfilters-search-placeholder": "Suodattimen viimeaikaiset muutokset (selaa tai aloita kirjoittaa)",
        "rcfilters-invalid-filter": "Suodatin on epäkelpo",
+       "rcfilters-empty-filter": "Ei aktiivisia suodattimia. Kaikki muutokset näytetään.",
        "rcfilters-filterlist-title": "Suodattimet",
        "rcfilters-filterlist-noresults": "Ei löytynyt suodattimia",
        "rcfilters-filter-registered-label": "Rekisteröitynyt",
        "rcfilters-filter-editsbyother-label": "Muiden muokkaukset",
        "rcfilters-filter-editsbyother-description": "Muutokset jotka tehneet muut käyttäjät (et sinä).",
        "rcfilters-filter-userExpLevel-newcomer-label": "Tulokkaat",
+       "rcfilters-filter-userExpLevel-learner-label": "Oppijat",
+       "rcfilters-filter-userExpLevel-experienced-label": "Kokeneet käyttäjät",
+       "rcfilters-filter-userExpLevel-experienced-description": "Enemmän kuin 30 päivää aktiivisena ja 500 muokkausta.",
+       "rcfilters-filtergroup-automated": "Automatisoidut muutokset",
        "rcfilters-filter-bots-label": "Botti",
        "rcfilters-filter-bots-description": "Muokkaukset jotka tehty automaattisilla työkaluilla.",
        "rcfilters-filter-humans-label": "Ihminen (ei botti)",
        "rcfilters-filter-minor-label": "Pienet muutokset",
+       "rcfilters-filter-major-label": "Ei-pienet muutokset",
+       "rcfilters-filter-major-description": "Muokkauksia ei ole merkitty kuin pieniksi.",
+       "rcfilters-filtergroup-changetype": "Muutoksen tyyppi",
+       "rcfilters-filter-pageedits-label": "Sivun muokkaukset",
+       "rcfilters-filter-pageedits-description": "Muokkaukset wikin sisältöön, keskusteluihin, luokkakuvauksiin....",
        "rcfilters-filter-newpages-description": "Muokkaukset jotka luovat uusia sivuja.",
        "rcfilters-filter-categorization-label": "Luokkamuutokset",
+       "rcfilters-filter-logactions-label": "Kirjatut toimet",
        "rcnotefrom": "Alla ovat muutokset <strong>$3, $4</strong> lähtien. (Enintään <strong>$1</strong> näytetään.)",
        "rclistfrom": "Näytä uudet muutokset $3 kello $2 alkaen",
        "rcshowhideminor": "$1 pienet muutokset",
        "booksources-invalid-isbn": "Annettu ISBN-numero ei ole kelvollinen. Tarkista alkuperäisestä lähteestä kirjoitusvirheiden varalta.",
        "magiclink-tracking-rfc": "Sivut, jotka käyttävät RFC-taikalinkkejä",
        "magiclink-tracking-pmid": "Sivut, jotka käyttävät PMID-taikalinkkejä",
-       "magiclink-tracking-isbn": "Sivut, joissa käytetään ISBN-taikalinkkejä",
+       "magiclink-tracking-isbn": "Sivut, jotka käyttävät ISBN-taikalinkkejä",
        "specialloguserlabel": "Suorittaja:",
        "speciallogtitlelabel": "Kohde (sivu tai {{ns:user}}:käyttäjänimi):",
        "log": "Lokit",
index adede88..a0ad98d 100644 (file)
        "virus-scanfailed": "skeniranje neuspješno (kod $1)",
        "virus-unknownscanner": "nepoznati antivirus:",
        "logouttext": "'''Odjavili ste se.'''\n\nNeke se stranice mogu prikazivati kao da ste još uvijek prijavljeni, sve dok ne očistite međuspremnik svog preglednika.",
-       "cannotlogoutnow-title": "Odjava trenutno nije moguća.",
+       "cannotlogoutnow-title": "Odjava trenutno nije moguća",
        "cannotlogoutnow-text": "Odjava nije moguća tijekom uporabe $1.",
        "welcomeuser": "Dobrodošli, $1!",
        "welcomecreation-msg": "Vaš je suradnički račun otvoren.\nNe zaboravite prilagoditi Vaše [[Special:Preferences|{{SITENAME}} postavke]].",
        "spamprotectionmatch": "Naš filtar spama reagirao je na sljedeći tekst: $1",
        "spambot_username": "MediaWiki zaštita od spama",
        "spam_reverting": "Vraćam na posljednju inačicu koja ne sadrži poveznice na $1",
-       "spam_blanking": "Sve inačice sadrže poveznice na $1, brišem cjelokupni sadržaj",
+       "spam_blanking": "Sve inačice koje sadržavaju poveznice na $1, brišem cjelokupni sadržaj",
        "spam_deleting": "Sve inačice sadržale su poveznice na $1, brišem cjelokupni sadržaj",
        "simpleantispam-label": "Anti-spam provjera.\n<strong>NE</strong> ispunjavajte ovo!",
        "pageinfo-title": "Podatci o stranici \"$1\"",
        "markedaspatrollederror-noautopatrol": "Ne možete vlastite promjene označiti patroliranima.",
        "markedaspatrollednotify": "Uređivanje stranice $1 označeno je pregledanim.",
        "markedaspatrollederrornotify": "Označavanje stranice pregledanom nije uspjelo.",
-       "patrol-log-page": "Evidencija pregledavanja promjena",
+       "patrol-log-page": "Evidencija ophodnji i samoophodnji",
        "patrol-log-header": "Ovo su evidencije ophođenih izmjena.",
-       "log-show-hide-patrol": "$1 evidenciju patroliranja",
+       "log-show-hide-patrol": "$1 evidenciju ophodnji",
+       "log-show-hide-tag": "$1 evidenciju oznaka",
        "confirm-markpatrolled-button": "U redu",
        "confirm-markpatrolled-top": "Označiti izmjenu $3 stranice $2 pregledanom?",
        "deletedrevision": "izbrisana stara inačica $1",
        "tag-mw-contentmodelchange": "promjena modela sadržaja",
        "tag-mw-contentmodelchange-description": "Uređivanja koja [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel mijenjanju model sadržaja] stranice",
        "tags-title": "Oznake",
-       "tags-intro": "Ova je stranica popis oznaka s kojima softver može označiti promjenu te njihovo značenje.",
+       "tags-intro": "Ova stranica sadržava popis oznaka s kojima programska oprema može označivati promjene te njihova značenja.",
        "tags-tag": "Naziv oznake",
        "tags-display-header": "Izgled na popisima izmjena",
        "tags-description-header": "Puni opis značenja",
        "tags-active-header": "Aktivno?",
        "tags-hitcount-header": "Označene izmjene",
        "tags-actions-header": "Radnje",
-       "tags-active-yes": "Da",
-       "tags-active-no": "Ne",
+       "tags-active-yes": "da",
+       "tags-active-no": "ne",
        "tags-source-extension": "Definirano proširenjem",
        "tags-source-none": "Nije više u uporabi",
        "tags-edit": "uredi",
        "tags-delete": "izbriši",
        "tags-activate": "pokreni",
        "tags-deactivate": "isključi",
-       "tags-hitcount": "$1 {{PLURAL:$1|promjena|promjene|promjena}}",
+       "tags-hitcount": "$1 {{PLURAL:$1|izmjena|izmjene|izmjena}}",
        "tags-manage-no-permission": "Nemate pravo upravljati promjenama oznaka.",
        "tags-create-heading": "Stvori novu oznaku",
        "tags-create-tag-name": "Naziv oznake:",
        "special-characters-group-ipa": "IPA",
        "special-characters-group-symbols": "simboli",
        "special-characters-group-greek": "grčki",
+       "special-characters-group-greekextended": "grčki (prošireno)",
        "special-characters-group-cyrillic": "ćirilica",
        "special-characters-group-arabic": "arapski",
+       "special-characters-group-arabicextended": "arapski (prošireno)",
        "special-characters-group-persian": "perzijski",
        "special-characters-group-hebrew": "hebrejski",
-       "special-characters-group-bangla": "Bangla znakovi",
+       "special-characters-group-bangla": "bengalski",
+       "special-characters-group-tamil": "tamilski",
        "special-characters-group-telugu": "telugu",
-       "special-characters-group-sinhala": "Sinhaleški znakovi",
-       "special-characters-group-gujarati": "Gudžaratski znakovi",
-       "special-characters-group-thai": "Tajlandski (tajski) znakovi",
-       "special-characters-group-lao": "laoski znakovi",
+       "special-characters-group-sinhala": "sinhaleški",
+       "special-characters-group-gujarati": "gudžaratski",
+       "special-characters-group-devanagari": "devanagari",
+       "special-characters-group-thai": "tajlandski (tajski)",
+       "special-characters-group-lao": "laoski",
        "special-characters-group-khmer": "kmerski",
        "mw-widgets-dateinput-placeholder-day": "GGGG-MM-DD",
        "mw-widgets-dateinput-placeholder-month": "GGGG-MM",
index cd7e0f6..b8e207c 100644 (file)
        "mw-widgets-titleinput-description-new-page": "pagina non existe ancora",
        "mw-widgets-titleinput-description-redirect": "redirection a $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Adder un categoria…",
+       "mw-widgets-usersmultiselect-placeholder": "Adder plus...",
        "sessionmanager-tie": "Impossibile combinar plure typos de authentication de requesta: $1.",
        "sessionprovider-generic": "sessiones $1",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "sessiones basate sur cookies",
index 57d0263..1814b46 100644 (file)
        "missingarticle-diff": "(Difero: $1, $2)",
        "internalerror": "Interna eroro",
        "internalerror_info": "Interna eroro: $1",
+       "internalerror-fatal-exception": "Ne-reparebla ecepto \"$1\"",
        "filecopyerror": "Ne povis kopiar l'arkivo \"$1\" a \"$2\".",
        "filerenameerror": "Ne povas rinomizar l'arkivo \"$1\" ad \"$2\".",
        "filedeleteerror": "Onu ne povis efacar l'arkivo \"$1\".",
+       "directorycreateerror": "Ne sucesis krear dosieruyo \"$1\".",
+       "directoryreadonlyerror": "Dosieruyo \"$1\" esas nur-lektebla.",
+       "directorynotreadableerror": "Dosieruyo \"$1\" esas ne-lektebla.",
        "filenotfound": "Onu ne povas trovar la arkivo \"$1\".",
        "unexpected": "Nevartita valoro: \"$1\"=\"$2\".",
        "formerror": "Eroro: Onu ne povis sendar la kontenajo di la formularo",
        "editingold": "'''EGARDEZ: Vu redaktas anciena versiono di ca pagino.\nSe vu gardus ol, la chanji facita pos ita revizo perdesos.'''",
        "yourdiff": "Diferi",
        "copyrightwarning": "Voluntez memorar ke omna kontributi a {{SITENAME}} esas sub la $2 (Videz $1 por detali).\nSe vu ne deziras ke altri modifikez vua artikli od oli distributesez libere, lore voluntez ne skribar oli hike.<br />\nPublikigante vua skribajo hike, vu asertas ke olu skribesis da vu ipsa o kopiesis de libera fonto.\n'''NE SENDEZ ARTIKLI KUN ''COPYRIGHT'' SEN PERMISO!'''",
-       "protectedpagewarning": "'''AVERTO: Ica pagino esas blokusita, do nur ''sysop''-i povas redaktar olu.'''",
+       "protectedpagewarning": "<strong>Averto: Ica pagino esas protektita por ke nur uzeri kun administero-yuri povas redaktar ol.</strong>\nLa maxim recenta en-registrago provizesas:",
        "templatesused": "{{PLURAL:$1|Shablono|Shabloni}} uzata en ica pagino:",
        "templatesusedpreview": "{{PLURAL:$1|Shablono|Shabloni}} uzata en ica prevido:",
        "templatesusedsection": "{{PLURAL:$1|Shablono|Shabloni}} uzata en ica seciono:",
        "move-page-legend": "Rinomizar pagino",
        "movepagetext": "Uzante ica formularo onu povas rinomizar pagino, movante olua omna versionaro ad la nova titulo.\nLa antea titulo konvertesos a ridirektilo a la nova titulo.\nLa ligili a la antea titulo dil pagino ne chanjesos.\nVoluntez certigar ke ne esas [[Special:DoubleRedirects|duopla]] o [[Special:BrokenRedirects|ruptota ridirektili]].\nVu responsas ke la ligili duros direktante a la pagino korespondanta.\n\nMemorez ke la pagino '''ne''' rinomizesos se ja existus pagino kun la nova titulo, eceptuante ke la pagino esas vakua o ridirektilo sen versionaro.\nIco signifikas ke vu povos rinomizar pagino a olua originala titulo se eroras skribante la nova titulo, ma ne povos riskribar existanta pagino.\n\n'''EGARDEZ!'''\nIca povas esar drastika chanjo e ne-esperinda por populara pagino;\nvoluntez certigar ke vu komprenas la konsequi qui eventos ante durar adavane.",
        "movenologintext": "Vu mustas esar registragita uzero ed [[Special:UserLogin|enirir]] por rinomizar pagino.",
-       "newtitle": "A nova titulo:",
+       "newtitle": "Nova titulo:",
        "move-watch": "Surveyar ca pagino",
        "movepagebtn": "Movar pagino",
        "pagemovedsub": "Rinomizita sucese",
        "thumbnail_error": "Ne sucesas krear imajeto: $1",
        "import": "Importacar pagini",
        "import-comment": "Komento:",
-       "importtext": "Voluntez exportacar l' arkivo de la fonto-wiki uzante la utensilo \"Special:Export\", registragar ol a vua disko ed adkargar ol hike.",
+       "importtext": "Voluntez exportacar l' arkivo de la fonto-wikio per [[Special:Export|exportacilo]]. Registragar ol a vua komputero ed adkargar ol hike.",
        "importfailed": "La importaco faliis: $1",
        "importsuccess": "Importaco sucesoza!",
        "tooltip-pt-userpage": "{{GENDER:|Vua uzero}} pagino",
index 78d0e07..8aaedb6 100644 (file)
        "youremail": "이메일:",
        "username": "{{GENDER:$1|사용자 이름}}:",
        "prefs-memberingroups": "{{GENDER:$2|소속}} {{PLURAL:$1|그룹}}:",
+       "group-membership-link-with-expiry": "$1 ($2 까지)",
        "prefs-registration": "등록 시간:",
        "yourrealname": "실명:",
        "yourlanguage": "언어:",
        "emailccsubject": "$1에게 보낸 메시지의 복사본: $2",
        "emailsent": "이메일 보냄",
        "emailsenttext": "이메일을 보냈습니다.",
-       "emailuserfooter": "이 이메일은 {{SITENAME}}의 $1님이 $2에게 \"{{int:emailuser}}\" 기능을 통해 보냈습니다.",
+       "emailuserfooter": "이 이메일은 {{SITENAME}}의 $1님이 $2에게 \"{{int:emailuser}}\" 기능을 통해 보냈습니다. 만약, {{GENDER:$2|당신}}이 이 이메일을 답장하면, {{GENDER:$2|당신}}의 이메일은 {{GENDER:$1|원래 사용자}}에게 {{GENDER:$2|당신}}의 이메일 주소가  {{GENDER:$1|원래 사용자}}에게 공개되면서 보내집니다.",
        "usermessage-summary": "시스템 메시지 남기기",
        "usermessage-editor": "시스템 메신저",
        "usermessage-template": "MediaWiki:UserMessage",
        "logentry-tag-update-logentry": "$1님이 $3 문서의 기록 항목 $5에 있는 태그를 {{GENDER:$2|업데이트했습니다}} ($6 {{PLURAL:$7|추가함}}; $8 {{PLURAL:$9|제거함}})",
        "rightsnone": "(없음)",
        "revdelete-summary": "편집 요약",
+       "rightslogentry-temporary-group": "$1 (일시적, $2까지)",
        "feedback-adding": "문서에 피드백을 올리는 중...",
        "feedback-back": "뒤로",
        "feedback-bugcheck": "감사합니다! 혹시 해당 사항이 [$1 기존의 버그 보고서]에 올라와 있는지 확인해주세요.",
        "usercssispublic": "주목해 주십시오: CSS의 하위 문서들은 다른 사용자들이 볼 수 있기 때문에 기밀 데이터를 포함해서는 안 됩니다.",
        "restrictionsfield-badip": "유효하지 않은 IP 주소나 대역: $1",
        "restrictionsfield-label": "허용된 IP 대역:",
-       "restrictionsfield-help": "줄 단위의 하나의 IP 주소 또는 CIDR 대역입니다. 모든 곳에 적용하려면, 다음을 사용하세요<br><code>0.0.0.0/0</code><br><code>::/0</code>",
+       "restrictionsfield-help": "줄 단위의 하나의 IP 주소 또는 CIDR 대역입니다. 모든 곳에 적용하려면, 다음을 사용하세요:<pre>0.0.0.0/0\n::/0</pre>",
        "revid": "$1 판",
        "pageid": "페이지 ID $1"
 }
index 7db3ad2..3699122 100644 (file)
        "history": "Dîroka rûpelê",
        "history_short": "Dîrok",
        "updatedmarker": "ji serdana min a dawî ve hate rojanekirin",
-       "printableversion": "Guhertoya bo çapkirinê",
+       "printableversion": "Guhertoya çapkirinê",
        "permalink": "Girêdana daîmî",
        "print": "Çap",
        "view": "Bibîne",
index 8459994..a204c39 100644 (file)
        "statistics-files": "Augšuplādētie faili",
        "statistics-edits": "Lapu izmaiņas kopš {{grammar:ģenitīvs{{SITENAME}}}} izveidošanas",
        "statistics-edits-average": "Vidējais izmaiņu skaits uz lapu",
-       "statistics-users": "Reģistrēti lietotāji",
+       "statistics-users": "Reģistrēti [[Special:ListUsers|dalībnieki]]",
        "statistics-users-active": "Aktīvi lietotāji",
        "statistics-users-active-desc": "Lietotāji, kas ir veikuši jebkādu darbību {{PLURAL:$1|iepriekšējās $1 dienās|iepriekšējā $1 dienā|iepriekšējās $1 dienās}}",
        "pageswithprop-prop": "Īpašības nosaukums:",
        "watchlist-submit": "Rādīt",
        "wlshowhideminor": "maznozīmīgos labojumus",
        "wlshowhidebots": "boti",
-       "wlshowhideliu": "reģistrēti lietotāji",
+       "wlshowhideliu": "reģistrēti dalībnieki",
        "wlshowhideanons": "anonīmi lietotāji",
        "wlshowhidepatr": "pārbaudīti labojumi",
        "wlshowhidemine": "mani labojumi",
index b6cebdf..6e9b480 100644 (file)
@@ -35,7 +35,7 @@
        "tog-enotifminoredits": "Andefasana imailaka na dia fanovana madinika aza no atao amin'ny pejy sy ny rakitra",
        "tog-enotifrevealaddr": "Asehoy ny adiresy imailako any amin'ny imailaka fampilazana",
        "tog-shownumberswatching": "Asehoy ny isan'ny mpikambana manara-maso ny pejy",
-       "tog-oldsig": "Topi-mason'ny sonia :",
+       "tog-oldsig": "Topi-mason'ny sonianao :",
        "tog-fancysig": "Sonia tsotra (tsy misy rohy)",
        "tog-uselivepreview": "Hampiasa ny topi-maso mivantana",
        "tog-forceeditsummary": "Teneno ahy ra tsy nametraka ny ambangovangony",
@@ -50,7 +50,7 @@
        "tog-ccmeonemails": "Andefaso tahaka ny imailaka alefako amin'ny mpikambana hafa",
        "tog-diffonly": "Aza ampiseho ny voatonin'ny pejy eo amban'ny diff",
        "tog-showhiddencats": "Asehoy ny sokajy misitrika",
-       "tog-norollbackdiff": "Aza aseho ny diff rehefa avy namafa fanàvana iray",
+       "tog-norollbackdiff": "Aza aseho ny fahasamihafana aorian'ny famoanana",
        "tog-useeditwarning": "Ampitandremo aho raha miala sady mamela pejy ovaiko nefa tsy notahiriziko",
        "tog-prefershttps": "Fanohizana azo antoka foana no ampaisaina rehefa tafiditra",
        "underline-always": "Foana foana",
        "newwindow": "(sokafy anaty takila hafa)",
        "cancel": "Aoka ihany",
        "moredotdotdot": "Tohiny...",
-       "morenotlisted": "Tsy feno ity lisitra ity.",
+       "morenotlisted": "Mety tsy feno ity lisitra ity.",
        "mypage": "Pejy",
        "mytalk": "Dinika",
        "anontalk": "Pejin-dresaka",
        "searcharticle": "Tsidiho",
        "history": "Tantaran'ny pejy",
        "history_short": "Tantara",
+       "history_small": "tantara",
        "updatedmarker": "niova hatry ny tsidiko farany",
        "printableversion": "Ny votoatiny azo atonta printy",
        "permalink": "Rohy maharitra",
        "talk": "dinika",
        "views": "Fijerena",
        "toolbox": "Fitaovana",
+       "tool-link-userrights": "Hanova ny vondron'i {{GENDER:$1|}}mpikambana",
+       "tool-link-userrights-readonly": "Hijery ny vondron'ny mpikambana{{GENDER:$1|}}",
+       "tool-link-emailuser": "Handefa mailaka an'ity mpikambana ity{{GENDER:$1|}}",
        "userpage": "Hijery ny pejy manokan'ny mpikambana",
        "projectpage": "Pejy meta",
        "imagepage": "Jereo ny pejin'ny sary",
        "createacct-yourpasswordagain-ph": "Mbola ampidiro fanindroany ny tenimiafinao",
        "userlogin-remembermypassword": "Tadidio aho",
        "userlogin-signwithsecure": "Fidirana amin'ny alalan'ny fanohizana azo antoka",
+       "cannotlogin-title": "Tsy afaka miditra",
+       "cannotlogin-text": "Tsy afaka atao ny fidirana",
        "cannotloginnow-title": "Tsy afaka miditra izao",
        "cannotloginnow-text": "Tsy afaka miditra rehefa mampiasa $1.",
+       "cannotcreateaccount-title": "Tsy afaka mamorona kaonty",
        "yourdomainname": "faritra (domaine) misy anao",
        "password-change-forbidden": "Tsy afaka manova ny tenimiafina ianao eto amin'ity wiki ity.",
        "externaldberror": "Nisy tsy fetezana angamba teo amin'ny fanamarinana anao tamin'ny sehatra ivelan'ity wiki ity, na tsy manana alalana hanova ny kaontinao ivelany ianao.",
        "noname": "Tsy nanome solonanarana mety ianao.",
        "loginsuccesstitle": "Tafiditra soa aman-tsara",
        "loginsuccess": "'''Tafiditra amin'ny {{SITENAME}} ianao ry \"$1\".'''",
-       "nosuchuser": "Tsy misy mpikambana manana izany solonanarana \"$1\" izany. Hamarino ny tsipelina na manokafa kaonty vaovao.",
+       "nosuchuser": "Tsy misy mpikambana manana izany solonanarana \"$1\" izany.\nManavaka ny renisoratra sy ny zanatsoratra ny anaram-pikambana.\nHamarino ny tsipelina na [[Special:CreateAccount|manokafa kaonty vaovao]].",
        "nosuchusershort": "Tsy misy mpikambana hoe \"$1\". Hamarino ny tsipelina.",
        "nouserspecified": "Tsy maintsy mampiditra solonanarana ianao.",
        "login-userblocked": "Voasakana io mpikambana io. Fidirana tsy nahazoan-dalana.",
        "noemail": "Tsy nanome adiresy imailaka i \"$1\".",
        "noemailcreate": "Tsy maintsy misy ny adiresy imailaka ho atsofokao",
        "passwordsent": "Nandefasana tenimiafina vaovao any amin'ny adiresy imailak'i \"$1\".\nAzafady midira rehefa voarainao io imailaka io.",
-       "blocked-mailpassword": "Voasakana ny adiresy IP-nao, nesorina aminao ny asa ''password recovery'' mba tsy hanararaotra.",
+       "blocked-mailpassword": "Voasakana tsy afa-manova ny adiresy IP-nao. Mba tsy hisian'ny fanararaotana dia tsy azo atao ny famerenana tenimiafina avy amin'ity adiresy IP ity.",
        "eauthentsent": "Efa nandefasana imailaka fanamarinana ilay adiresy nomenao.\nAlohan'ny handefasana mailaka hafa any amin'ity kaonty ity dia mila manaraka ny torolalana ianao hahafahana manamarina anao ho tompon'ilay kaonty.",
        "throttled-mailpassword": "Efa nandefasana mailaka famerenana tenimiafiana ianao tanatin'ny {{PLURAL:$1|ora|$1 ora}}.\nMba tsy hisian'ny fanararaotana dia mailaka famerenana tenimiafiana iray ihany no azo ampiasaina isaky ny adin'ny $1{{PLURAL:}}.",
        "mailerror": "Nisy olana tamin'ny fandefasana imailaka: $1",
-       "acct_creation_throttle_hit": "Miala tsiny, efa nanokatra kaonty miisa $1 ianao, ka tsy afaka mamorona hafa intsony.{{PLURAL:}}",
+       "acct_creation_throttle_hit": "Ireo mpitsidika avy amin'ny adiresy IP-nao dia namorona {{PLURAL:$1|kaonty iray|kaonty $1}} tanatin'ny $2, izay isam-panoronana farafahabetsany azo atao anatin'izay fe-potoana izay.\nNy vokatr'izany dia tsy afa-mamorona kaonty vaovao ity adiresy ity amin'izao fotoana izao.",
        "emailauthenticated": "Voamarina tamin'ny $2 tamin'ny $3 ny adiresy mailakao.",
        "emailnotauthenticated": "Tsy mbola nomarinina ny adiresy mailakao.\nTsy handefa mailaka izy ho an'ireo asa ireo.",
        "noemailprefs": "Manomeza adiresy imailaka raha hampiasa ireo fitaovana ireo ianao.",
        "botpasswords-insert-failed": "Tsy afaka nanampy ny anarana rôbô \"$1\". Tsy efa nampiana ve ilay izy?",
        "botpasswords-update-failed": "Tsy afaka nanavao ny anarana rôbô \"$1\". Nofafàna ve ilay izy?",
        "botpasswords-created-title": "Noforonina ilay tenimiafina rôbô",
-       "botpasswords-created-body": "Noforonina ny tenimiafina rôbô \"$1\".",
+       "botpasswords-created-body": "Noforonina ny tenimiafina rôbô \"$1\" an'ny mpikambana \"$2\".",
        "botpasswords-updated-title": "Nohavaozina ny tenimiafina rôbô",
-       "botpasswords-updated-body": "Nohavaozina ny tenimiafina rôbô \"$1\".",
+       "botpasswords-updated-body": "Nohavaozina ny tenimiafina rôbô \"$1\" an'ny mpikambana \"$2\".",
        "botpasswords-deleted-title": "Nofafàna ny tenimiafina rôbô",
-       "botpasswords-deleted-body": "Nofafàna ny tenimiafina rôbô \"$1\".",
+       "botpasswords-deleted-body": "Nofafàna ny tenimiafina rôbô \"$1\" an'ny mpikambana \"$2\".",
        "botpasswords-newpassword": "Ny tenimiafina idirana amin'i <strong>$1</strong> dia <strong>$2</strong>. <em>Raiketo ilay izy ho an'ny fampiasàna any aoriana any.</em>",
        "botpasswords-no-provider": "Tsy afaka antsoina ny BotPasswordsSessionProvider.",
        "botpasswords-restriction-failed": "Manakana ity fidirana ity ny fepetra mifehy ny tenimiafina rôbô.",
        "userrights-nodatabase": "Tsy eto akaiky na tsy misy ny banky angona « $1 ».",
        "userrights-changeable-col": "Ny gropy azonao ovaina",
        "userrights-unchangeable-col": "Ny gropy tsy azonao ovaina",
+       "userrights-expiry-current": "Mitsahatra ny $1",
+       "userrights-expiry": "Fitsaharana:",
        "userrights-conflict": "Fifandonana fanovana zom-pikambana! Avereno vakiana ary marino ny fanovanao.",
        "group": "Gropy :",
        "group-user": "Mpikambana",
index 9f3f1f3..865fa33 100644 (file)
@@ -40,6 +40,7 @@
        "tog-watchdefault": "Tambahkan laman-laman dan fail-fail yang saya sunting ke dalam senarai pantau saya",
        "tog-watchmoves": "Tambahkan laman-laman dan fail-fail yang saya pindah ke dalam senarai pantau saya",
        "tog-watchdeletion": "Tambahkan laman-laman dan fail-fail yang saya hapuskan ke dalam senarai pantau saya",
+       "tog-watchuploads": "Tambahkan fail baru yang saya muat naik ke senarai pantauan",
        "tog-watchrollback": "Tambahkan laman-laman yang saya undurkan ke dalam senarai pantau saya",
        "tog-minordefault": "Tandakan semua suntingan sebagai kecil secara lalai",
        "tog-previewontop": "Tunjukkan pralihat di atas kotak sunting",
@@ -49,7 +50,7 @@
        "tog-enotifminoredits": "Juga e-melkan saya untuk suntingan-suntingan laman dan fail yang kecil",
        "tog-enotifrevealaddr": "Serlahkan alamat e-mel saya dalam e-mel pemberitahuan",
        "tog-shownumberswatching": "Tunjukkan bilangan pemantau",
-       "tog-oldsig": "Tanda tangan yang sedia ada:",
+       "tog-oldsig": "Tandatangan yang sedia ada:",
        "tog-fancysig": "Anggap tandatangan sebagai teks wiki (tanpa pautan automatik)",
        "tog-uselivepreview": "Gunakan prebiu langsung",
        "tog-forceeditsummary": "Tanya saya jika ringkasan suntingan kosong",
        "october-date": "$1 Oktober",
        "november-date": "$1 November",
        "december-date": "$1 Disember",
+       "period-am": "AM",
+       "period-pm": "PM",
        "pagecategories": "{{PLURAL:$1|Kategori|Kategori}}",
        "category_header": "Laman-laman dalam kategori \"$1\"",
        "subcategories": "Subkategori",
        "category-file-count-limited": "$1 fail berikut terdapat dalam kategori ini.",
        "listingcontinuesabbrev": "samb.",
        "index-category": "Laman terindeks",
-       "noindex-category": "Laman tak diindeks",
+       "noindex-category": "Laman tak diindekskan",
        "broken-file-category": "Laman yang ada pautan fail yang terputus",
        "about": "Perihal",
        "article": "Laman kandungan",
        "newwindow": "(dibuka di tetingkap baru)",
        "cancel": "Batal",
        "moredotdotdot": "Lagi...",
-       "morenotlisted": "Senarai ini tidak lengkap.",
+       "morenotlisted": "Senarai ini mungkin tidak lengkap.",
        "mypage": "Halaman",
        "mytalk": "Perbincangan",
        "anontalk": "Perbincangan",
        "searcharticle": "Pergi",
        "history": "Sejarah laman",
        "history_short": "Sejarah",
+       "history_small": "sejarah",
        "updatedmarker": "dikemaskinikan sejak kunjungan terakhir saya",
        "printableversion": "Versi boleh cetak",
        "permalink": "Pautan kekal",
        "talk": "Perbincangan",
        "views": "Rupa",
        "toolbox": "Peralatan",
+       "tool-link-userrights": "Tukar kumpulan {{GENDER:$1|pengguna}}",
+       "tool-link-userrights-readonly": "Lihat kumpulan {{GENDER:$1|pengguna}}",
+       "tool-link-emailuser": "Email {{GENDER:$1|pengguna}} ini",
        "userpage": "Lihat laman pengguna",
        "projectpage": "Lihat laman projek",
        "imagepage": "Lihat laman fail",
        "databaseerror-query": "Pertanyaan: $1",
        "databaseerror-function": "Fungsi: $1",
        "databaseerror-error": "Ralat: $1",
-       "transaction-duration-limit-exceeded": "Demi mengelakkan terjadinya jeda pengulangan yang tinggi, transaksi ini dibatalkan kerana tempoh menulis ($1) telah melebihi had $2 saat.",
+       "transaction-duration-limit-exceeded": "Demi mengelakkan terjadinya kelewatan pengulangan yang tinggi, transaksi ini dibatalkan kerana tempoh menulis ($1) telah melebihi had $2 saat.",
        "laggedslavemode": "Amaran: Laman ini mungkin bukan yang terkini.",
        "readonly": "Pangkalan data dikunci",
        "enterlockreason": "Sila nyatakan sebab penguncian dan jangkaan\nbila kunci ini akan dibuka.",
        "virus-scanfailed": "pengimbasan gagal (kod $1)",
        "virus-unknownscanner": "antivirus tidak dikenali:",
        "logouttext": "'''Anda telah log keluar.'''\n\nSila ingat bahawa sesetengah halaman mungkin masih dipaparkan seolah-olah anda masih log masuk hingga anda memadamkan cache pelayar anda.",
+       "cannotlogoutnow-title": "Tidak boleh melog keluar sekarang",
+       "cannotlogoutnow-text": "Melog keluar tidak boleh dilakukan apabila menggunakan $1",
        "welcomeuser": "Selamat datang, $1!",
        "welcomecreation-msg": "Akaun anda telah dibuka.\nJangan lupa untuk mengubah [[Special:Preferences|keutamaan anda di {{SITENAME}}]].",
        "yourname": "Nama pengguna:",
        "createacct-yourpasswordagain-ph": "Isikan kata laluan semula",
        "userlogin-remembermypassword": "Biar saya kekal log masuk",
        "userlogin-signwithsecure": "Gunakan sambungan terlindung",
+       "cannotlogin-title": "Tidak boleh melog masuk",
+       "cannotlogin-text": "Tidak mungkin boleh melog masuk.",
+       "cannotloginnow-title": "Tidak boleh melog masuk sekarang",
+       "cannotloginnow-text": "Melog masuk tidak boleh dilakukan apabila menggunakan $1",
+       "cannotcreateaccount-title": "Tidak boleh mencipta akaun",
+       "cannotcreateaccount-text": "Penciptaan akaun langsung tidak diaktifkan di wiki ini.",
        "yourdomainname": "Domain anda:",
        "password-change-forbidden": "Anda tidak dapat mengubah kata laluan di wiki ini.",
        "externaldberror": "Berlaku ralat pangkalan data bagi pengesahan luar atau anda tidak dibenarkan mengemaskinikan akaun luar anda.",
        "login": "Log masuk",
+       "login-security": "Mengesahkan identiti anda",
        "nav-login-createaccount": "Log masuk / buka akaun",
        "userlogin": "Log masuk / buka akaun",
        "userloginnocreate": "Log masuk",
        "userlogin-resetpassword-link": "Lupa kata laluan anda?",
        "userlogin-helplink2": "Bantuan untuk log masuk",
        "userlogin-loggedin": "Anda sudah log masuk sebagai {{GENDER:$1|$1}}. Gunakan borang di bawah untuk log masuk sebagai pengguna lain.",
+       "userlogin-reauth": "Anda mesti log masuk sekali lagi untuk mengesahkan bahawa anda adalah {{GENDER:$1|$1}}.",
        "userlogin-createanother": "Buka satu lagi akaun",
        "createacct-emailrequired": "Alamat e-mel",
        "createacct-emailoptional": "Alamat e-mel (pilihan)",
        "createacct-email-ph": "Isikan alamt e-mel anda",
        "createacct-another-email-ph": "Masukkan alamat e-mel",
        "createaccountmail": "Gunakan kata laluan rawak yang sementara dan hantarnya ke alamat e-mel yang dinyatakan",
+       "createaccountmail-help": "Boleh digunakan untuk membuat akaun untuk orang lain tanpa mengetahui kata laluan.",
        "createacct-realname": "Nama sebenar (pilihan)",
        "createaccountreason": "Sebab:",
        "createacct-reason": "Sebab",
        "createacct-reason-ph": "Mengapa anda membuka satu lagi akaun",
+       "createacct-reason-help": "Mesej yang ditunjukkan dalam log penciptaan akaun",
        "createacct-submit": "Wujudkan akaun anda",
        "createacct-another-submit": "Buka akaun",
+       "createacct-continue-submit": "Teruskan penciptaan akaun",
+       "createacct-another-continue-submit": "Teruskan penciptaan akaun",
        "createacct-benefit-heading": "{{SITENAME}} dijayakan oleh orang ramai seperti anda.",
        "createacct-benefit-body1": "{{PLURAL:$1|suntingan}}",
        "createacct-benefit-body2": "{{PLURAL:$1|halaman}}",
        "nocookieslogin": "{{SITENAME}} menggunakan ''cookies'' untuk mencatat status log masuk pengguna. Sila aktifkan sokongan ''cookies'' pada pelayar anda dan cuba lagi.",
        "nocookiesfornew": "Akaun pengguna tidak dicipta kerana kami tidak dapat sahkan sumbernya.\nPastikan anda telah bolehkan kuki, muat semula laman ini dan cuba lagi.",
        "nocookiesforlogin": "{{int:nocookieslogin}}",
+       "createacct-loginerror": "Akaun ini telah berjaya dicipta tetapi anda tidak boleh log masuk secara automatik. Sila ke [[Special:UserLogin|log masuk manual]].",
        "noname": "Nama pengguna tidak sah.",
-       "loginsuccesstitle": "Berjaya log masuk",
+       "loginsuccesstitle": "Berjaya melog masuk",
        "loginsuccess": "'''Anda telah log masuk ke dalam {{SITENAME}} sebagai \"$1\".'''",
-       "nosuchuser": "Pengguna \"$1\" tidak wujud. Nama pengguna adalah peka huruf besar. Sila semak ejaan anda, atau anda boleh [[Special:CreateAccount|membuka akaun baru]].",
+       "nosuchuser": "Tiada pengguna yang menggunakan nama \"$1\".\nNama pengguna adalah kes sensitif.\nSemak ejaan anda, atau sila [[Special:CreateAccount|membuka akaun baru]].",
        "nosuchusershort": "Pengguna \"$1\" tidak wujud. Sila semak ejaan anda.",
        "nouserspecified": "Sila nyatakan nama pengguna.",
        "login-userblocked": "Pengguna ini disekat. Log masuk tidak dibenarkan.",
        "noemail": "Tiada alamat e-mel direkodkan bagi pengguna \"$1\".",
        "noemailcreate": "Anda perlu memberikan alamat e-mel sah",
        "passwordsent": "Kata laluan baru telah dikirim kepada alamat\ne-mel yang didaftarkan oleh \"$1\".\nSila log masuk semula setelah anda menerima e-mel tersebut.",
-       "blocked-mailpassword": "Alamat IP anda telah disekat daripada sebarang penyuntingan, oleh itu, untuk\nmengelak penyalahgunaan, anda tidak dibenarkan menggunakan ciri pemulihan kata laluan.",
+       "blocked-mailpassword": "Alamat IP anda disekat dari menyunting. Untuk mengelakkan penyalahgunaan, ia tidak dibenarkan untuk menggunakan pemulihan kata laluan dari alamat IP ini.",
        "eauthentsent": "Sepucuk e-mel pengesahan telah dikirim kepada alamat e-mel yang dinyatakan.\nSebelum e-mel lain boleh dikirim kepada alamat tersebut, anda perlu mematuhi arahan-arahan pada e-mel pengesahan tersebut untuk mengesahkan bahawa alamat tersebut benar-benar kepunyaan anda.",
        "throttled-mailpassword": "E-mel set semula kata laluan telah dihantar dalam tempoh $1 jam yang lalu.\nUntuk mencegah salah guna, hanya sepucuk e-mel set semula kata laluan dihantar setiap {{PLURAL:$1|jam|$1 jam}}.",
        "mailerror": "Ralat ketika mengirim e-mel: $1",
-       "acct_creation_throttle_hit": "Pengunjung wiki ini yang menggunakan alamat IP anda telah membuka sebanyak $1 akaun semenjak sehari lepas, iaitu merupakan had maksimum yang dibenarkan dalam tempoh tersebut.\nAkibatknya, pengunjung dari alamat IP ini tidak boleh membuka akaun lagi pada masa sekarang.",
+       "acct_creation_throttle_hit": "Pelawat yang datang ke wiki ini menggunakan alamat IP anda telah mencipta $1 akaun dalam $2 terakhir, iaitu maksimum yang dibenarkan dalam tempoh masa ini.\nHasilnya, pelawat menggunakan alamat IP ini tidak boleh membuat apa-apa lebih akaun pada masa ini.",
        "emailauthenticated": "Alamat e-mel anda telah disahkan pada $2, $3.",
        "emailnotauthenticated": "Alamat e-mel anda belum disahkan. Oleh itu, e-mel tidak boleh dikirim bagi ciri-ciri berikut.",
        "noemailprefs": "Anda perlu menetapkan alamat e-mel terlebih dahulu untuk menggunakan ciri-ciri ini.",
        "createaccount-title": "Pembukaan akaun {{SITENAME}}",
        "createaccount-text": "Seseorang telah membuka akaun untuk\nalamat e-mel anda di {{SITENAME}} ($4) dengan nama \"$2\" dan kata laluan \"$3\".\nAnda boleh log masuk dan tukar kata laluan anda sekarang.\n\nSila abaikan mesej ini jika anda tidak meminta untuk membuka akaun tersebut.",
        "login-throttled": "Anda telah mencuba log masuk berulang kali.\nSila tunggu $1 dan cuba lagi.",
-       "login-abort-generic": "Log masuk anda tidak berjaya, dan terpaksa dibatalkan",
+       "login-abort-generic": "Log masuk anda tidak berjaya - Dibatalkan",
        "login-migrated-generic": "Akaun anda telah dipindahkan dan nama pengguna anda tidak lagi wujud di wiki ini.",
        "loginlanguagelabel": "Bahasa: $1",
        "suspicious-userlogout": "Permintaan anda untuk log keluar ditolak kerana ia kelihatan seperti dihantar oleh pelayar rosak atau proksi pengagregatan.",
        "createacct-another-realname-tip": "Nama sebenar adalah tidak wajib.\nJika anda memilih untuk menyatakannya, ini akan digunakan untuk memberikan atribusi kepada pengguna atas sumbangan mereka.",
        "pt-login": "Log masuk",
        "pt-login-button": "Log masuk",
+       "pt-login-continue-button": "Teruskan melog masuk",
        "pt-createaccount": "Buka akaun",
        "pt-userlogout": "Log keluar",
        "php-mail-error-unknown": "Ralat tak diketahui dalam fungsi mail() PHP",
        "newpassword": "Kata laluan baru:",
        "retypenew": "Ulangi kata laluan baru:",
        "resetpass_submit": "Tetapkan kata laluan dan log masuk",
-       "changepassword-success": "Kata laluan anda berjaya ditukar!",
+       "changepassword-success": "Kata laluan anda sudah ditukar!",
        "changepassword-throttled": "Anda telah melakukan terlalu banyak cubaan log masuk baru-baru ini.\nSila tunggu $1 dan cuba lagi.",
+       "botpasswords": "Kata laluan bot",
+       "botpasswords-summary": "<em>Kata laluan bot</em> membenarkan akses kepada akaun pengguna melalui API tanpa menggunakan butiran log masuk utama akaun. Hak-hak pengguna yang apabila log masuk dengan kata laluan bot mungkin terhad.\n\nJika anda tidak tahu mengapa anda mungkin mahu untuk melakukan ini, anda perlu mungkin tidak melakukannya. Tiada siapa yang pernah meminta kepada kamu untuk menjana satu ini dan memberikannya kepada mereka.",
+       "botpasswords-disabled": "Kata laluan bot dilumpuhkan.",
+       "botpasswords-no-central-id": "Untuk menggunakan kata laluan bot, anda mesti log masuk ke akaun berpusat.",
+       "botpasswords-existing": "Kata laluan bot yang sedia ada",
+       "botpasswords-createnew": "Buat kata laluan bot baru",
+       "botpasswords-editexisting": "Ubah kata laluan bot yang sedia ada",
+       "botpasswords-label-appid": "Nama bot:",
+       "botpasswords-label-create": "Cipta",
+       "botpasswords-label-update": "Kemas kini",
+       "botpasswords-label-cancel": "Batalkan",
+       "botpasswords-label-delete": "Hapuskan",
+       "botpasswords-label-resetpassword": "Set semula kata laluan",
+       "botpasswords-label-grants": "Pemberian berkenaan:",
        "resetpass_forbidden": "Kata laluan tidak boleh ditukar",
        "resetpass-no-info": "Anda hendaklah log masuk terlebih dahulu untuk mencapai laman ini secara terus.",
        "resetpass-submit-loggedin": "Tukar kata laluan",
index d105b06..4ee9915 100644 (file)
        "search-interwiki-caption": "Søsterprosjekter",
        "search-interwiki-default": "Resultater fra $1:",
        "search-interwiki-more": "(mer)",
+       "search-interwiki-more-results": "flere resultater",
        "search-relatedarticle": "Relatert",
        "searchrelated": "relatert",
        "searchall": "alle",
        "editusergroup": "Last brukergrupper",
        "editinguser": "Endrer brukerrettighetene for {{GENDER:$1|bruker}} <strong>[[User:$1|$1]]</strong> $2",
        "viewinguserrights": "Viser {{GENDER:$1|brukerrettighetene}} til  <strong>[[User:$1|$1]]</strong> $2",
-       "userrights-editusergroup": "Rediger brukergrupper",
-       "userrights-viewusergroup": "Se brukergrupper",
+       "userrights-editusergroup": "Rediger {{GENDER:$1|brukergrupper}}",
+       "userrights-viewusergroup": "Se {{GENDER:$1|brukergrupper}}",
        "saveusergroups": "Lagre {{GENDER:$1|brukergrupper}}",
        "userrights-groupsmember": "Medlem av:",
        "userrights-groupsmember-auto": "Implisitt medlem av:",
-       "userrights-groups-help": "Du kan endre hvilke grupper denne brukeren er medlem av.\n* En avkrysset boks betyr at brukeren er medlem av gruppen.\n* En uavkrysset boks betyr at brukeren ikke er medlem av gruppen.\n* En * betyr at du ikke kan fjerne gruppemedlemskapet når du har lagt det til, eller vice versa.",
+       "userrights-groups-help": "Du kan endre hvilke grupper denne brukeren er medlem av.\n* En avkrysset boks betyr at brukeren er medlem av gruppen.\n* En uavkrysset boks betyr at brukeren ikke er medlem av gruppen.\n* En * betyr at du ikke kan fjerne gruppemedlemskapet når du har lagt det til, eller vice versa.\n* En # betyr at du kun kan forkorte utløpstiden til denne gruppen, du kan ikke forlenge den.",
        "userrights-reason": "Årsak:",
        "userrights-no-interwiki": "Du har ikke tillatelse til å endre brukerrettigheter på andre wikier.",
        "userrights-nodatabase": "Databasen $1 finnes ikke, eller er ikke lokal.",
        "userrights-expiry-options": "1 dag:1 day,1 uke:1 week,1 måned:1 month,3 måneder:3 months,6 måneder:6 months,1 år:1 year",
        "userrights-invalid-expiry": "Utløpstiden for gruppa «$1» er ugyldig.",
        "userrights-expiry-in-past": "Utløpstiden for gruppa «$1» har vært.",
+       "userrights-cannot-shorten-expiry": "Du kan ikke forlenge utløpstiden til gruppa «$1». Bare brukere med tillatelse til å legge til eller fjerne denne gruppa kan forlenge utløpstider.",
        "userrights-conflict": "En konflikt med endringen av brukerrettigheter! Vær vennlig å sjekke og på nytt bekrefte endringene dine.",
        "group": "Gruppe:",
        "group-user": "Brukere",
        "htmlform-user-not-exists": "<strong>$1</strong> eksisterer ikke.",
        "htmlform-user-not-valid": "<strong>$1</strong> er ikke et gyldig brukernavn.",
        "logentry-delete-delete": "$1 {{GENDER:$2|slettet}} siden $3",
-       "logentry-delete-delete_redir": "$1 {{GENDER:$2|sletta}} omdirigeringa $3 gjennom overskriving",
+       "logentry-delete-delete_redir": "$1 {{GENDER:$2|slettet}} omdirigeringen $3 ved å overskrive",
        "logentry-delete-restore": "$1 {{GENDER:$2|gjenopprettet}} siden $3",
        "logentry-delete-event": "$1 {{GENDER:$2|endret}} synligheten av {{PLURAL:$5|en logghendelse|$5 logghendelser}} på $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|endret}} synligheten av {{PLURAL:$5|en revisjon|$5 revisjoner}} på side $3: $4",
        "mw-widgets-titleinput-description-new-page": "siden eksisterer ikke ennå",
        "mw-widgets-titleinput-description-redirect": "omdiriger til $1",
        "mw-widgets-categoryselector-add-category-placeholder": "Legg til en kategori …",
+       "mw-widgets-usersmultiselect-placeholder": "Legg til flere ...",
        "sessionmanager-tie": "Kan ikke kombinere flere forespørselsautentiseringstyper: $1",
        "sessionprovider-generic": "$1 sesjoner",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "informasjons&shy;kapsel-baserte sesjoner",
index c3f20c9..c72547c 100644 (file)
        "htmlform-yes": "Ja",
        "htmlform-chosen-placeholder": "Vel ein",
        "logentry-delete-delete": "$1 {{GENDER:$2|sletta}} sida $3",
+       "logentry-delete-delete_redir": "$1 {{GENDER:$2|sletta}} omdirigeringa $3 gjennom overskriving",
        "logentry-delete-restore": "$1 {{GENDER:$2|attoppretta}} sida $3",
        "logentry-delete-event": "$1 {{GENDER:$2|endra}} synlegdomen av {{PLURAL:$5|éi loggoppføring|$5 loggoppføringar}} på $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|endra}} synlegdomen til {{PLURAL:$5|éin versjon|$5 versjonar}} på sida $3: $4",
        "limitreport-cputime": "CPU-tidsbruk",
        "limitreport-cputime-value": "{{PLURAL:$1|eitt sekund|$1 sekund}}",
        "limitreport-walltime-value": "{{PLURAL:$1|eitt sekund|$1 sekund}}",
-       "limitreport-ppvisitednodes": "Nodevitjingar av preprosessor",
+       "limitreport-ppvisitednodes": "Nodar vitja av preprosessor",
        "limitreport-ppgeneratednodes": "Nodar laga av preprosessor",
        "limitreport-postexpandincludesize": "Inkluderingsstorleik etter utviding",
        "limitreport-postexpandincludesize-value": "$1/$2 byte",
index e26610f..8479f6f 100644 (file)
        "october": "d'octobre",
        "november": "de novembre",
        "december": "de decembre",
-       "january-gen": "Genièr",
-       "february-gen": "Febrièr",
-       "march-gen": "Març",
+       "january-gen": "de genièr",
+       "february-gen": "de febrièr",
+       "march-gen": "de març",
        "april-gen": "Abril",
-       "may-gen": "Mai",
-       "june-gen": "Junh",
-       "july-gen": "Julhet",
+       "may-gen": "de mai",
+       "june-gen": "de junh",
+       "july-gen": "de julhet",
        "august-gen": "d'agost",
-       "september-gen": "Setembre",
+       "september-gen": "de setembre",
        "october-gen": "Octobre",
-       "november-gen": "Novembre",
-       "december-gen": "Decembre",
+       "november-gen": "de novembre",
+       "december-gen": "de decembre",
        "jan": "de gen",
        "feb": "de feb",
        "mar": "de març",
        "internalerror_info": "Error intèrna: $1",
        "internalerror-fatal-exception": "Error fatala de tipe \"$1\"",
        "filecopyerror": "Impossible de copiar lo fichièr « $1 » cap a « $2 ».",
-       "filerenameerror": "Impossible de tornar nomenar lo fichièr « $1 » en « $2 ».",
+       "filerenameerror": "Impossible de renomenar lo fichièr « $1 » en « $2 ».",
        "filedeleteerror": "Impossible de suprimir lo fichièr « $1 ».",
        "directorycreateerror": "Impossible de crear lo dorsièr « $1 ».",
        "directoryreadonlyerror": "Lo repertòri « $1 » es en lectura sola.",
        "no-null-revision": "Impossible de crear una novèla revision voida per la pagina « $1 »",
        "badtitle": "Títol marrit",
        "badtitletext": "Lo títol de la pagina demandada es invalid, void o s’agís d’un títol interlenga o interprojècte mal ligat. Benlèu conten un o maites caractèrs que pòdon pas èsser utilizats dins los títols.",
-       "title-invalid-talk-namespace": "La pagina de títol demandada fa referéncia a una pagina de discussion qu'existís pas.",
-       "title-invalid-characters": "La pagina de títol demandada contèn de caractèrs invalides : $1",
+       "title-invalid-talk-namespace": "Lo títol de la pagina demandada fa referéncia a una pagina de discussion que pòt pas existir.",
+       "title-invalid-characters": "Lo títol  de la pagina demandada conten de caractèrs invalids : « $1 ».",
        "perfcached": "Las donadas seguendas son en cache e benlèu, son pas a jorn. Un maximum de {{PLURAL:$1|un resultat|$1 resultats}} es disponible dins lo cache.",
        "perfcachedts": "Las donadas seguendas son en cache e benlèu, son pas a jorn. Un maximum de {{PLURAL:$1|un resultat|$1 resultats}} es disponible dins lo cache.",
        "querypage-no-updates": "Las mesas a jorn per aquesta pagina son actualamnt desactivadas. Las donadas çaijós son pas mesas a jorn.",
        "viewyourtext": "Podètz veire e copiar lo contengut de <strong>vòstras modificacions</strong> a aquesta pagina.",
        "protectedinterface": "Aquesta pagina provesís de tèxte d’interfàcia pel logicial susaqueste wiki, e es protegida per evitar los abuses.\nPer apondre o modificar de traduccions sus totes los wikis, utilizatz [https://translatewiki.net/ translatewiki.net], lo projècte de localizacion de MediaWiki.",
        "editinginterface": "<strong>Atencion :<strong> sètz a mand de modificar una pagina utilizada per crear lo tèxte de l’interfàcia del logicial.\nLos cambiaments sus aquesta pagina se repercutaràn sus l'aparéncia de l'interfàcia d'utilizaire pels autres utilizaires d'aqueste wiki.",
-       "cascadeprotected": "Aquesta pagina es actualament protegida perque es inclusa dins {{PLURAL:$1|la pagina seguenta|las paginas seguentas}}, {{PLURAL:$1|qu'es estada protegida|que son estadas protegidas}} amb l’opcion « proteccion en cascada » activada :\n$2",
+       "cascadeprotected": "Aquesta pagina es protegida perque es inclusa dins {{PLURAL:$1|la pagina seguenta qu'es estada protegida|las paginas seguentas que son estadas protegidas}} amb l’opcion « proteccion en cascada » activada :\n$2",
        "namespaceprotected": "Avètz pas la permission de modificar las paginas de l’espaci de noms « '''$1''' ».",
        "customcssprotected": "Avètz pas la permission d'editar aquesta pagina CSS perque conten de preferéncias d’autres utilizaires.",
        "customjsprotected": "Avètz pas la permission d'editar aquesta pagina JavaScript perque conten de preferéncias d’autres utilizaires.",
        "myprivateinfoprotected": "Avètz pas los dreits per modificar vòstras informacions personalas.",
        "mypreferencesprotected": "Avètz pas los dreits per modificar vòstras preferéncias.",
        "ns-specialprotected": "Las paginas dins l’espaci de noms « {{ns:special}} » pòdon pas èsser modificadas",
-       "titleprotected": "Aqueste títol es estat protegit a la creacion per [[User:$1|$1]].\nLo motiu avançat es <em>$2</em>.",
+       "titleprotected": "Aqueste títol es estat protegit contra tota creacion per [[User:$1|$1]].\nLo motiu avançat es <em>$2</em>.",
        "filereadonlyerror": "Impossible de modificar lo fichièr « $1 » perque lo repertòri de fichièrs « $2 » es en lectura sola.\n\nL'administrator del sistèma que l'a verrolhat a provesit aqueste motiu : « $3 ».",
        "invalidtitle-knownnamespace": "Títol invalid amb l'espaci de noms « $2 » e l'intitulat « $3 »",
        "invalidtitle-unknownnamespace": "Títol invalid amb lo numèro d'espaci de noms $1 e l'intitulat « $2 » desconeguts",
        "createaccountreason": "Motiu :",
        "createacct-reason": "Motiu",
        "createacct-reason-ph": "Perqué creatz un autre compte",
+       "createacct-reason-help": "Messatge afichat dins lo jornal de creacion de compte",
        "createacct-submit": "Creatz vòstre compte",
        "createacct-another-submit": "Crear un compte",
        "createacct-continue-submit": "Contunhar la creacion de compte",
        "nocookiesnew": "Lo compte d'utilizaire es estat creat, mas sètz pas connectat. {{SITENAME}} utiliza de cookies per la connexion mas los avètz desactivats. Activatz-los e reconnectatz-vos amb lo meteis nom e lo meteis senhal.",
        "nocookieslogin": "{{SITENAME}} utiliza de cookies per la connexion mas avètz los cookies desactivats. Activatz-los e reconnectatz-vos.",
        "nocookiesfornew": "Lo compte d'utilizaire es pas estat creat, perque avèm pas pogut identificar son origina.\nVerificatz qu'avètz activat los cookies, recargatz la pagina e tornatz ensajar.",
+       "createacct-loginerror": "Lo compte es estat creat corrèctament mas vos podètz pas connectar automaticament. [[Special:UserLogin|Connectatz-vos manualament]].",
        "noname": "Avètz pas picat de nom d'utilizaire valid.",
        "loginsuccesstitle": "Connectat",
        "loginsuccess": "Sètz actualament connectat(ada) sus {{SITENAME}} en tant que « $1 ».",
        "noemail": "Cap d'adreça electronica es pas estada enregistrada per l'utilizaire « $1 ».",
        "noemailcreate": "Vos cal provesir una adreça de corrièl valida",
        "passwordsent": "Un senhal novèl es estat mandat a l'adreça electronica de l'utilizaire « $1 ».\nIdentificatz-vos tre que l'aurètz recebut.",
-       "blocked-mailpassword": "Vòstra adreça IP es blocada en edicion, la foncion de rapèl del senhal es doncas desactivada per evitar los abuses.",
+       "blocked-mailpassword": "Vòstra adreça IP es blocada en edicion. Per evitar los abuses, es pas autorizat d'utilizar la recuperacion de senhal a partir d'aquesta adreça IP.",
        "eauthentsent": "Un corrièr de confirmacion es estat mandat a l’adreça indicada.\nAbans qu’un autre corrièr sià mandat a aqueste compte, vos caldrà seguir las instruccions donadas dins lo messatge per confirmar que lo compte es plan vòstre.",
        "throttled-mailpassword": "Un corrièr electronic de reïnicializacion de vòstre senhal es ja estat mandat durant {{PLURAL:$1|la darrièra ora|las $1 darrièras oras}}. Per evitar los abuses, un sol corrièr de reïnicializacion de vòstre senhal serà pas mandat per {{PLURAL:$1|ora|interval de $1 oras}}.",
        "mailerror": "Error en mandant lo corrièr electronic : $1",
-       "acct_creation_throttle_hit": "De visitors d'aqueste wiki qu'utilizan vòstra adreça IP an creat $1 {{PLURAL:$1|compte|comptes}} lo jorn darrièr, aquò es lo limit maximum autorizat pendent aqueste periòde.\nAtal los visitors qu'utilizan aquesta adreça IP pòdon pas crear mai de compte novèl pel moment.",
+       "acct_creation_throttle_hit": "Los visitors d'aqueste wiki qu'utilizan vòstra adreça IP an creat {{PLURAL:$1|compte|comptes}} durant las darrièras $2, aquò es lo limit maximum autorizat pendent aqueste periòde.\nEn consequéncia, la creacion de compte pels visitors qu'utilizan aquesta adreça IP es temporàriament suspenduda.",
        "emailauthenticated": "Vòstra adreça de corrièr electronic es estada confirmada lo $2 a $3.",
        "emailnotauthenticated": "Vòstra adreça de corrièr electronic es pas encara confirmada. Cap de corrièr serà pas mandat per caduna de las foncions seguentas.",
        "noemailprefs": "Cap d'adreça electronica es pas estada indicada, las foncions seguentas seràn pas disponiblas.",
        "changepassword-throttled": "Avètz ensajat un tròp grand nombre de connexions darrièrament.\nEsperatz $1 abans d’ensajar tornarmai.",
        "botpasswords": "Senhals de robòts",
        "botpasswords-disabled": "Los senhals robòts son desactivats.",
-       "botpasswords-no-central-id": "Per intrar lo senhau d'un bot, devètz èsser connectat amb un còmpte globau.",
+       "botpasswords-no-central-id": "Per utilizar los senhals de robòts, vos cal èsser connectat a un compte centralizat.",
        "botpasswords-existing": "Senhals de robòts existents",
-       "botpasswords-createnew": "Crear un novèu senhau de bot",
-       "botpasswords-editexisting": "Editar un senhau de bot existent",
+       "botpasswords-createnew": "Crear un novèl senhal de robòts",
+       "botpasswords-editexisting": "Modificar un senhal de robòts existent",
        "botpasswords-label-appid": "Nom del robòt :",
        "botpasswords-label-create": "Crear",
        "botpasswords-label-update": "Metre a jorn",
        "botpasswords-label-grants-column": "Acordat",
        "botpasswords-bad-appid": "Lo nom del robòt «$1» es pas valid.",
        "botpasswords-insert-failed": "Fracàs de l’apondon del nom de robòt « $1 ». Es ja estat apondut ?",
-       "botpasswords-created-title": "Senhau de bot creat",
-       "botpasswords-created-body": "Lo senhau dau bot per lo bot $1 de l'utilizaire $2 es estat creat",
-       "botpasswords-updated-title": "Senhau dau bot més a jorn",
-       "botpasswords-updated-body": "Lo senhau dau bot $1 de l'utilizaire $2 es estat més a jorn",
-       "botpasswords-deleted-title": "Senhau dau bot escafat",
-       "botpasswords-deleted-body": "Lo senhay dau bot $1 de l'utilizaire $2 es estat escafat",
-       "botpasswords-no-provider": "BotPasswordsSessionProvider es pas disponible",
+       "botpasswords-created-title": "Senhal de robòts creat",
+       "botpasswords-created-body": "Lo senhal pel robòt « $1 » de l'utilizaire « $2 » es estat creat.",
+       "botpasswords-updated-title": "Senhal de robòts mes a jorn",
+       "botpasswords-updated-body": "Lo senhal pel robòt « $1 » de l'utilizaire « $2 » es estat mes a jorn.",
+       "botpasswords-deleted-title": "Senhal de robòts suprimit",
+       "botpasswords-deleted-body": "Lo senhal pel robòt « $1 » de l'utilizaire « $2 » es estat suprimit.",
+       "botpasswords-no-provider": "BotPasswordsSessionProvider es pas disponible.",
        "resetpass_forbidden": "Los senhals pòdon pas èsser cambiats",
-       "resetpass_forbidden-reason": "Lei senhaus pòdon pas èsser cambiats : $1",
+       "resetpass_forbidden-reason": "Los senhaus pòdon pas èsser cambiats : $1",
        "resetpass-no-info": "Vos cal èsser connectat per aver accès a aquesta pagina.",
        "resetpass-submit-loggedin": "Modificar lo senhal",
        "resetpass-submit-cancel": "Anullar",
        "passwordreset-emailtext-ip": "Qualqu'un (probablament vos, dempuèi l'adreça IP $1) a demandat una reïnicializacion de vòstre senhal per {{SITENAME}} ($4). {{PLURAL:$3|Lo compte d'utilizaire seguent es associat|Los comptes d'utilizaires seguents son associats}} a aquesta adreça de corrièr electronic :\n\n$2\n\n{{PLURAL:$3|Aqueste senhal temporari expirarà|Aquestes senhals temporaris expiraràn}} dins {{PLURAL:$5|un jorn|$5 jorns}}. Ara, vos cal vos connectar e causir un senhal novèl. Se aquesta demanda proven pas de vos, o que vos sètz remembrat de vòstre senhal inicial, e que volètz pas mai lo modificar, podètz ignorar aqueste messatge e contunhar d'utilizar vòstre ancian senhal.",
        "passwordreset-emailtext-user": "L'utilizaire $1 sus {{SITENAME}} a demandat una reïnicializacion de vòstre senhal per {{SITENAME}} ($4). {{PLURAL:$3|Lo compte d'utilizaire seguent es associat|Los comptes d'utilizaires seguents son associats}} a aquesta adreça de corrièr electronic :\n\n$2\n\n{{PLURAL:$3|Aqueste senhal temporari expirarà|Aquestes senhals temporaris expiraràn}} dins {{PLURAL:$5|un jorn|$5 jorns}}. Ara, vos cal vos connectar e causir un senhal novèl. Se aquesta demanda proven pas de vos, o que vos sètz remembrat de vòstre senhal inicial, e que lo volètz pas mai modificar, podètz ignorar aqueste messatge e contunhar d'utilizar vòstre ancian senhal.",
        "passwordreset-emailelement": "Utilizaire: \n$1\n\nSenhal temporari: \n$2",
-       "passwordreset-emailsentemail": "Se aquela adreiça de corrièr electrnic es associat ambé vòstre compte, un corrièr electronic de reïnicializacion de senhal es estat mandat.",
-       "passwordreset-emailsentusername": "Se una adreiça de corrier electronic es associada amb aqueu còmpte d'utilizaire, un senhau de reïnicializacion serà mandat.",
+       "passwordreset-emailsentemail": "Se aquesta adreça de corrièl es associada a vòstre compte, alara un corrièl de reïnicializacion de senhal serà mandat.",
+       "passwordreset-emailsentusername": "Se i a una adreça de corrièr electronic associada a aqueste nom d’utilizaire, alara un corrièl de reïnicializacion senhal serà mandat.",
        "passwordreset-nosuchcaller": "L’apelant existís pas : $1",
-       "passwordreset-invalidemail": "Adreiça electronica invalida",
+       "passwordreset-invalidemail": "Adreça de corrièr electronic invalida",
        "changeemail": "Cambiar o suprimir l'adreça electronica",
-       "changeemail-header": "Cambiar l'adreça electronica del compte",
+       "changeemail-header": "Completatz aqueste formulari per modificar vòstra adreça de corrièl. Se volètz suprimir l’associacion d’una adreça de corrièl amb vòstre compte, daissatz la novèla adreça de corrièl voida al moment de la somission del formulari.",
        "changeemail-no-info": "Vos cal èsser connectat per aver accès a aquesta pagina.",
        "changeemail-oldemail": "Adreça electronica actuala:",
        "changeemail-newemail": "Novela adreça electronica:",
        "accmailtext": "Un senhal generat aleatòriament per [[User talk:$1|$1]] es estat mandat a $2.\nLo senhal per aqueste compte novèl pòt èsser cambiat sus la pagina ''[[Special:ChangePassword|Cambiament de senhal]]'' aprèp connexion.",
        "newarticle": "(Novèl)",
        "newarticletext": "Avètz seguit un ligam cap a una pagina qu’existís pas encara o qu'es estada [{{fullurl:Special:Log|type=delete&page={{FULLPAGENAMEE}}}} escafada].\nPer crear aquesta pagina, picatz vòstre tèxte dins la bóstia çaijós (podètz consultar [$1 la pagina d’ajuda] per mai d’entresenhas).\nSe sètz arribat(ada) aicí per error, clicatz sul boton '''retorn''' de vòstre navigador.",
-       "anontalkpagetext": "---- ''Sètz sus la pagina de discussion d'un utilizaire anonim qu'a pas encara creat un compte o que n'utiliza pas.\nPer aquesta rason, devèm utilizar son adreça IP per l'identificar. Una adreça d'aqueste tipe pòt èsser partejada entre mantun utilizaire. Se sètz un utilizaire anonim e se constatatz que de comentaris que vos concernisson pas vos son estats adreçats, podètz [[Special:CreateAccount|crear un compte]] o [[Special:UserLogin|vos connectar]] per evitar tota confusion venenta amb d’autres contributors anonims.''",
+       "anontalkpagetext": "----\n<em>Sètz sus la pagina de discussion d'un utilizaire anonim qu'a pas encara creat un compte o que n'utiliza pas<em>.\nPer aquesta rason, nos cal utilizar son adreça IP per l'identificar.\nUna adreça d'aqueste tipe pòt èsser partejada entre mantun utilizaire.\nSe sètz un{{GENDER:||a|}} utilizair{{GENDER:|e|a|e}} anonim{{GENDER:||a|}} e se constatatz que de comentaris que vos concernisson pas vos son estats adreçats, podètz [[Special:CreateAccount|crear un compte]] o [[Special:UserLogin|vos connectar]] per evitar tota confusion venenta amb d’autres contributors anonims.",
        "noarticletext": "Pel moment, i a pas cap de tèxte sus aquesta pagina.\nPodètz [[Special:Search/{{PAGENAME}}|aviar una recèrca sul títol d'aqueste títol de pagina]] dins las autras pagina,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} recercar dins las operacions ligadas],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear aquesta pagina]</span>.",
        "noarticletext-nopermission": "Actualament i a pas cap de tèxte dins aquesta pagina.\nPodètz [[Special:Search/{{PAGENAME}}|far una recèrca sul títol de la pagina]] dins las autras paginas,\no <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} recercar dins los jornals associats]</span>.",
        "missing-revision": "La revision n° $1 de la pagina intitulada « {{FULLPAGENAME}} » existís pas.\n\nAquò se produtz en general en seguent un ligam istoric obsolèt cap a una pagina qu'es estada suprimida.\nPodètz trobar mai de detalhs dins lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornal de las supressions].",
        "userpage-userdoesnotexist": "Lo compte d'utilizaire « <nowiki>$1</nowiki> » es pas enregistrat. Indicatz se volètz crear o editar aquesta pagina.",
        "userpage-userdoesnotexist-view": "Lo compte d'utilizaire « $1 » es pas enregistrat.",
        "blocked-notice-logextract": "Aqueste utilizaire es actualament blocat.\nLa darrièra entrada del jornal dels blocatges es indicada çaijós a títol d’informacion :",
-       "clearyourcache": "'''Nòta :''' Aprèp aver enregistrat vòstras modificacions, es possible que vos calga forçar lo recargament complet de l'escondedor de vòstre navigador per veire los cambiaments : \n*'''Firefox / Safari :''' mantenètz la tòca ''Majuscula'' (''Shift'') en clicant lo boton ''Actualizar'' (''Reload,'') o quichatz sus ''Ctrl-F5'' o ''Ctrl-R''  (''⌘-R'' sus un Mac)\n* '''Google Chrome:''' Quichatz sus ''Ctrl-Shift-R'' (''⌘-Shift-R'' sus un Mac) \n* '''Internet Explorer:''' Mantenètz la tòca ''Ctrl'' en clicant sul boton ''Actualizar'', o quichatz ''Ctrl-F5'' \n* '''Opera:''' Voidatz l'escondedor dins ''Aisinas → Preferéncias''",
+       "clearyourcache": "<strong>Nòta</strong>: Aprèp aver enregistrat vòstras modificacions, es possible que vos calga forçar lo recargament complet de l'escondedor de vòstre navigador per veire los cambiaments : \n* <strong>Firefox / Safari :</strong> mantenètz la tòca <em>Maj</em> (<em>Shift</em>) en clicant lo boton <em>Actualizar</em> o quichatz sus <em>Ctrl-F5</em> o <em>Ctrl-R</em>  (<em>⌘-R</em> sus un Mac)\n* <strong>Google Chrome :</strong> Quichatz sus <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> sus un Mac) \n* <strong>Internet Explorer :</strong> Mantenètz la tòca <em>Ctrl</em> en clicant sul boton <em>Actualizar</em>, o quichatz <em>Ctrl-F5</em> \n* <strong>Opera :</strong> anatz dins <em>Menú → Settings</em> (<em>Opera → Preferéncias</em> sus un Mac) e puèi a <em>Confidencialitat & seguretat → Escafar las donadas d’exploracion → Imatges e fichièrs en cache</em>.",
        "usercssyoucanpreview": "'''Astúcia :''' Utilizatz lo boton 'Previsualizacion' per testar vòstre fuèlh css/js novèl abans de l'enregistrar.",
        "userjsyoucanpreview": "'''Astúcia :''' Utilizatz lo boton 'Previsualizacion' per testar vòstre fuèlh css/js novèl abans de l'enregistrar.",
        "usercsspreview": "'''Remembratz-vos que sètz a previsualizar vòstre pròpri fuèlh CSS !'''\n'''Es pas estada encara enregistrada !'''",
        "search-interwiki-caption": "Projèctes fraires",
        "search-interwiki-default": "Resultats de $1 :",
        "search-interwiki-more": "(mai)",
+       "search-interwiki-more-results": "mai de resultats",
        "search-relatedarticle": "Relatat",
        "searchrelated": "relatat",
        "searchall": "Totes",
        "showingresultsinrange": "Afichar çaijós fins a {{PLURAL:$1|<strong>1</strong> resultat|<strong>$1</strong> resultats}} dins la seria #<strong>$2</strong> a #<strong>$3</strong>.",
        "search-showingresults": "{{PLURAL:$4|Resultat <strong>$1</strong> demest <strong>$3</strong>|Resultats <strong>$1 a $2</strong> demest <strong>$3</strong>}}",
        "search-nonefound": "I a pas cap de resultat correspondent a la requèsta.",
+       "search-nonefound-thiswiki": "I a pas de resultats que correspondon a la requèsta sus aqueste site.",
        "powersearch-legend": "Recèrca avançada",
        "powersearch-ns": "Recercar dins los espacis de nom :",
        "powersearch-togglelabel": "Marcar :",
        "youremail": "Adreça de corrièr electronic :",
        "username": "{{GENDER:$1|Nom d'utilizaire|Nom d'utilizaira}}:",
        "prefs-memberingroups": "{{GENDER:$2|Membre|Membra}} {{PLURAL:$1|del grop|dels gropes}}:",
+       "group-membership-link-with-expiry": "$1 (fins a $2)",
        "prefs-registration": "Data de creacion del compte :",
        "yourrealname": "Nom vertadièr :",
        "yourlanguage": "Lenga de l'interfàcia :",
        "email": "Corrièr electronic",
        "prefs-help-realname": "Lo nom vertadièr es facultatiu.\nSe l'especificatz, serà utilizat per vos atribuir vòstras contribucions.",
        "prefs-help-email": "L’adreça de corrièr electronic es facultativa mas vos permet de reçaupre lo senhal se lo doblidatz.\nTanben podètz causir de permetre a d’autres de vos contactar per vòstra pagina d’utilizaire o la de discussion sens sofracha de desvelar vòstra idenditat.",
-       "prefs-help-email-others": "Tanben podètz causir de daissar los autres vos contactar sus vòstra pagina de discussion d'utilizaire sens que siá necessari de revelar vòstra identitat.",
+       "prefs-help-email-others": "Tanben podètz causir de daissar los autres vos contactar per corrièl via un ligam sus vòstra pagina de discussion d'utilizaire o pagina d'utilizaire.\nVòstra adreça de corrièr electronic es pas revelada quand los utilizaires vos contactan.",
        "prefs-help-email-required": "Una adreça de corrièr electronic es requesa.",
        "prefs-info": "Informacion de basa",
        "prefs-i18n": "Internationalizacion",
        "saveusergroups": "Enregistrar los gropes de l’{{GENDER:$1|utilizaire|utilizaira}}",
        "userrights-groupsmember": "Membre de :",
        "userrights-groupsmember-auto": "Membre implicit de :",
-       "userrights-groups-help": "Podètz modificar los gropes alsquals aparten aqueste utilizaire.\n* Una casa marcada significa que l'utilizaire se tròba dins aqueste grop.\n* Una casa pas marcada significa, al contrari, que s’i tròba pas.\n* Una * indica que podretz pas levar aqueste grop un còp que l'auretz apondut e vice-versa.",
+       "userrights-groups-help": "Podètz modificar los gropes als quals aparten {{GENDER:$1|aqueste utilizaire|aquesta utilizaira}}.\n* Una casa marcada significa que l'utilizair{{GENDER:$1|e|a}} se tròba dins aqueste grop.\n* Una casa pas marcada significa, al contrari, que s’i tròba pas.\n* Un asterisc * indica que podètz pas levar aqueste grop un còp que l'avètz apondut e vice-versa.\n* Un dièsi (#) indica que podètz que ralongar lo delai d'expiracion del grop e non pas l'acorchir.",
        "userrights-reason": "Motiu :",
        "userrights-no-interwiki": "Sètz pas abilitat per modificar los dreits dels utilizaires sus d'autres wikis.",
        "userrights-nodatabase": "La basa de donadas « $1 » existís pas o es pas en local.",
        "userrights-expiry": "Data d’expiracion :",
        "userrights-expiry-existing": "Data d'expiracion existenta : $2 à $3",
        "userrights-expiry-othertime": "Autre temps :",
+       "userrights-expiry-options": "1 jorn:1 day,1 setmana:1 week,1 mes:1 month,3 meses:3 months,6 meses:6 months,1 an:1 year",
        "userrights-conflict": "Conflicte de modificacion de dreits d'utilizaire ! Relegissètz e confirmatz vòstras modificacions.",
        "group": "Grop :",
        "group-user": "Utilizaires",
        "right-minoredit": "Marcar de cambiaments coma menors",
        "right-move": "Renomenar de paginas",
        "right-move-subpages": "Desplaçar de paginas amb lor sospaginas",
-       "right-move-rootuserpages": "Tornar nomenar las paginas de l’utilizaire de banca.",
+       "right-move-rootuserpages": "Renomenar la pagina principala d’un utilizaire",
        "right-move-categorypages": "Renomenar de paginas de categoria",
        "right-movefile": "Desplaçar los fichièrs",
        "right-suppressredirect": "Crear pas de redireccion dempuèi la pagina anciana en renomenant la pagina",
        "grant-group-file-interaction": "Interagir amb de mèdias",
        "grant-group-watchlist-interaction": "Interagir amb vòstra lista de seguiment",
        "grant-group-email": "Mandar un corrièr electronic",
+       "grant-group-customization": "Personalizacion e preferéncias",
+       "grant-group-administration": "Efectuar d'accions administrativas",
+       "grant-group-private-information": "Accedir a vòstras donadas privadas",
+       "grant-group-other": "Activitats divèrsas",
        "grant-blockusers": "Blocar e desblocar d'utilizaires",
        "grant-createaccount": "Crear de comptes",
        "grant-createeditmovepage": "Crear, modificar e desplaçar de paginas",
-       "grant-editpage": "Editar lei paginas existentas",
-       "grant-editprotected": "Editar lei paginas protegidas",
+       "grant-editmyoptions": "Modificar vòstras preferéncias d'utilizaire",
+       "grant-editpage": "Modificar de paginas existentas",
+       "grant-editprotected": "Modificar de paginas protegidas",
        "grant-patrol": "Verificar las modificacions de paginas",
-       "grant-uploadeditmovefile": "Telecargar, remplaçar e desplaçar de fichiers",
-       "grant-uploadfile": "Telecargar un novèu fichier",
+       "grant-privateinfo": "Accedir a las informacions privadas",
+       "grant-sendemail": "Mandar de corriers electronics als autres utilizaires",
+       "grant-uploadeditmovefile": "Telecargar, remplaçar e renomenar de fichièrs",
+       "grant-uploadfile": "Importar de fichièrs novèls",
        "grant-basic": "Dreits de basa",
-       "grant-viewdeleted": "Veire lei fichiers e lei paginas escafats",
+       "grant-viewdeleted": "Afichar los fichièrs e paginas suprimits",
        "grant-viewmywatchlist": "Afichar vòstra lista de seguiment",
        "newuserlogpage": "Istoric de las creacions de comptes",
        "newuserlogpagetext": "Jornal de las creacions de comptes d'utilizaires.",
        "action-history": "afichar l’istoric d'aquesta pagina",
        "action-minoredit": "marcar aqueste cambiament coma menor",
        "action-move": "renomenar aquesta pagina",
-       "action-move-subpages": "tornar nomenar aquesta pagina e sas sospaginas",
+       "action-move-subpages": "renomenar aquesta pagina e sas sospaginas",
        "action-move-rootuserpages": "renomenar las paginas de l’utilizaire de basa.",
        "action-move-categorypages": "Renomenar de paginas de categoria",
        "action-movefile": "renomenar aqueste fichièr",
        "action-writeapi": "utilizar l‘API d’escritura",
        "action-delete": "suprimir aquesta pagina",
        "action-deleterevision": "suprimir las revisions",
-       "action-deletelogentry": "Escafar lo jornau deis intradas",
+       "action-deletelogentry": "suprimir las entradas del jornal",
        "action-deletedhistory": "veire l’istoric suprimit d'una pagina",
        "action-browsearchive": "recercar de paginas suprimidas",
        "action-undelete": "restablir de paginas",
        "action-userrights-interwiki": "modificar los dreits d’utilizaire e los sus d’autres wikis",
        "action-siteadmin": "verrolhar o desverrolhar la basa de donadas",
        "action-sendemail": "mandar corrièrs electronics",
-       "action-editmyoptions": "Editar vòstrei preferéncias",
+       "action-editmyoptions": "modificar vòstras preferéncias",
        "action-editmywatchlist": "modificar vòstra lista de seguiment",
        "action-viewmywatchlist": "afichar vòstra pròpria lista de seguiment",
        "action-viewmyprivateinfo": "veire vòstras informacions personalas",
        "action-editmyprivateinfo": "modificar vòstras informacions personalas",
-       "action-purge": "Purgar la pagina",
+       "action-purge": "purgar aquesta pagina",
        "nchanges": "$1 {{PLURAL:$1|cambiament|cambiaments}}",
        "enhancedrc-since-last-visit": "$1 {{PLURAL:$1|dempuèi la darrièra visita}}",
        "enhancedrc-history": "istoric",
        "rcfilters-filtergroup-automated": "Contribucions automatizadas",
        "rcfilters-filter-bots-label": "Robòt",
        "rcfilters-filter-humans-label": "Èsser uman (pas robòt)",
+       "rcfilters-filter-humans-description": "Modificacions faitas per d'editors umans.",
        "rcfilters-filtergroup-significance": "Significacion",
        "rcfilters-filter-minor-label": "Cambiaments menors",
+       "rcfilters-filter-minor-description": "Modificacions que l'autor a marcadas coma menoras.",
        "rcfilters-filter-major-label": "Modificacions pas menoras",
+       "rcfilters-filter-major-description": "Modificacions pas marcadas coma menoras.",
        "rcfilters-filtergroup-changetype": "Tipe de cambiament",
        "rcfilters-filter-pageedits-label": "Modificacions de pagina",
+       "rcfilters-filter-pageedits-description": "Modificacions del contengut del wiki, de las discussions, de las descripcions de las categorias...",
        "rcfilters-filter-newpages-label": "Creacions de pagina",
+       "rcfilters-filter-newpages-description": "Modificacions a l'origina de paginas novèlas.",
        "rcfilters-filter-categorization-label": "Cambiaments de categoria",
        "rcfilters-filter-logactions-label": "Accions traçadas",
+       "rcfilters-filter-logactions-description": "Accions dels administrators, creacions de comptes, supressions de paginas, telecargaments...",
        "rcnotefrom": "Çaijós {{PLURAL:$5|la modificacion efectuada|las modificacions efectuadas}} dempuèi lo <strong>$3, $4</strong> (afichadas fins a <strong>$1</strong>).",
        "rclistfrom": "Afichar las modificacions novèlas dempuèi lo $3 $2",
        "rcshowhideminor": "$1 los cambiaments menors",
        "recentchangeslinked-page": "Nom de la pagina :",
        "recentchangeslinked-to": "Afichar los cambiaments cap a las paginas ligadas al luòc de la pagina donada",
        "recentchanges-page-added-to-category": "[[:$1]] apondut a la categoria",
-       "recentchanges-page-removed-from-category": "[[:$1]] retirat de la categoria",
-       "autochange-username": "Cambiament automatic MediaWiki",
+       "recentchanges-page-removed-from-category": "[[:$1]] suprimit de la categoria",
+       "autochange-username": "Cambiament automatic de MediaWiki",
        "upload": "Importar un fichièr",
        "uploadbtn": "Importar un fichièr",
        "reuploaddesc": "Anullar lo cargament e tornar al formulari.",
        "file-thumbnail-no": "Lo nom del fichièr comença per <strong>$1</strong>.\nEs possible que s’agisca d’una version reducha ''(miniatura)''.\nSe dispausatz del fichièr en resolucion nauta, importatz-lo, si que non cambiatz lo nom del fichièr.",
        "fileexists-forbidden": "Un fichièr amb aqueste nom existís ja e pòt pas èsser espotit.\nSe volètz totjorn importar aquel fichièr, mercé de tornar en arrièr e d'utilizar un nom novèl. [[File:$1|thumb|center|$1]]",
        "fileexists-shared-forbidden": "Un fichièr amb lo meteis nom existís ja dins la basa de donadas comuna.\nS'o volètz importar tornamai, tornatz en rèire e importatz-lo jos un autre nom. [[File:$1|thumb|center|$1]]",
-       "fileexists-no-change": "Lo telecargament es un doblon de <strong>[[:$1]]</strong>.",
-       "fileexists-duplicate-version": "Lo telecargament es un doblon de {{PLURAL:$2|an older version|older versions}} de <strong>[[:$1]]</strong>.",
+       "fileexists-no-change": "Lo fichièr telecargat es una còpia exacta de la version actuala de <strong>[[:$1]]</strong>",
+       "fileexists-duplicate-version": "Lo fichièr mandat es una còpia exacta {{PLURAL:$2|d'una version precedenta|de versions precedentas}} de <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "Aqueste fichièr es un doble {{PLURAL:$1|del fichièr seguent|dels fichièrs seguents}} :",
        "file-deleted-duplicate": "Un fichièr identic a aqueste ([[:$1]]) ja es estat suprimit. Vos caldriá verificar lo jornal de las supressions d'aqueste fichièr abans de la tornar telecargar.",
        "file-deleted-duplicate-notitle": "Un fichièr identic a aqueste fichièr es ja estat suprimit amai lo títol. \nVos caldriá demandar a qualqu'un la possibilitat de verificar lo jornal d'aqueste fichièr suprimit per tal d'examinar la situacion  abans de l'importar tornarmai.",
        "upload-form-label-own-work": "Soi l'autor d'aquesta òbra",
        "upload-form-label-infoform-categories": "Categorias",
        "upload-form-label-infoform-date": "Data",
-       "upload-form-label-own-work-message-generic-foreign": "Compreni que siáu a telecargar aquest fichier vèrs un estocatge partejat. Confiermi que siáu a lo faire segon lei reglas d'utilizacion e de licéncia en vigor.",
-       "upload-form-label-not-own-work-message-generic-foreign": "Se siatz pas capable de telecargar aqust fichir segon lei reglas d'aquest estocatge partejat, mercé de sarrar aquest boita de dialògue a d'assaiar un autre metòde.",
+       "upload-form-label-not-own-work-local-generic-local": "Tanben podètz ensajar [[Special:Upload|la pagina de telecargament per defaut]].",
+       "upload-form-label-own-work-message-generic-foreign": "Compreni que mandi aqueste fichièr cap a un depaus partejat. Confirmi qu'agissi en acòrd amb las condicions d'utilizacion e las règlas relativas a las licéncias en vigor.",
+       "upload-form-label-not-own-work-message-generic-foreign": "Se sètz pas en capacitat de mandar aqueste fichièr segon las règlas d'aqueste depaus partejat, mercé de tampar aquesta bóstia de dialòg a d'ensajar un autre metòde.",
        "backend-fail-stream": "Impossible de legir lo fichièr $1.",
        "backend-fail-backup": "Impossible de salvar lo fichièr $1.",
        "backend-fail-notexists": "Lo fichièr $1 existís pas.",
        "zip-bad": "Lo fichièr es un archiu ZIP corromput o illegible.\nPòt pas èsser verificat corrèctament per la seguretat.",
        "zip-unsupported": "Lo fichièr es un archiu ZIP qu'utiliza de caracteristicas pas suportadas per MediaWiki. \nSa seguretat pòt pas èsser verificada corrèctament.",
        "uploadstash": "Cache d'impòrt",
-       "uploadstash-summary": "La pagina dona accès ai fichiers que son telecargats o en cors de telecargament, mai pas encara publicats sus lo wiki. Aquelei fichiers son unicament visibles per l'utilizaire a l'origina dau telecargament.",
+       "uploadstash-summary": "Aquesta pagina dona accès als fichièrs que son importats (o en cors d'importacion), mas son pas encara publicats dins lo wiki. Aqueles fichièrs son pas encara visibles, levat per l'utilizaire a l'origina de l'importacion.",
        "uploadstash-clear": "Escafar los fichièrs en cache",
        "uploadstash-nofiles": "Avètz pas de fichièrs en cache d'impòrt.",
        "uploadstash-errclear": "La supression dels fichièrs a fracassat.",
        "listfiles-delete": "suprimir",
        "listfiles-summary": "Aquesta pagina especiala permet de far la lista de totes los fichièrs importats.",
        "listfiles_search_for": "Recèrca del mèdia nomenat :",
-       "listfiles-userdoesnotexist": "L'utilizaire \"$1\" es pas enregistrat.",
+       "listfiles-userdoesnotexist": "Lo compte d'utilizaire « $1 » es pas enregistrat.",
        "imgfile": "fichièr",
        "listfiles": "Lista dels imatges",
        "listfiles_thumb": "Apercebut",
        "uploadnewversion-linktext": "Importar una version novèla d'aqueste fichièr",
        "shared-repo-from": "de $1",
        "shared-repo": "un depaus partejat",
+       "shared-repo-name-wikimediacommons": "Wikimèdia Commons",
        "upload-disallowed-here": "Podètz pas remplaçar aqueste fichièr.",
        "filerevert": "Revocar $1",
        "filerevert-legend": "Revocar lo fichièr",
        "filerevert-submit": "Revocar",
        "filerevert-success": "'''[[Media:$1|$1]]''' es estat revocat fins a [$4 la version del $2 a $3].",
        "filerevert-badversion": "I a pas de version mai anciana del fichièr amb lo Timestamp donat.",
-       "filerevert-identical": "La version actuala d'aqueu fichier es ja identica an aquela qu'es seleccionada.",
+       "filerevert-identical": "La version actuala del fichièr es ja identica a aquela qu'es seleccionada.",
        "filedelete": "Suprimir $1",
        "filedelete-legend": "Suprimir lo fichièr",
        "filedelete-intro": "Sètz a suprimir '''[[Media:$1|$1]]''' amb tot son istoric.",
        "unusedimages": "Imatges orfanèls",
        "wantedcategories": "Categorias mai demandadas",
        "wantedpages": "Paginas mai demandadas",
-       "wantedpages-summary": "Aquesta pagina compta totes los ligams, encluses los inserits via de modèls de navigacion. Per una lista dels articles mai demandats, vejatz articles mai demandats, que comptabiliza pas que las referéncias que provenon de l'espaci de nom dels articles. Vejatz tanben lo '''articles mai demandats'''.",
+       "wantedpages-summary": "Lista de las paginas inexistentas qu'an lo mai de ligams cap a elas, en excluent las paginas qu'an pas que de redireccions puntant cap a elas. Per aver una lista de las paginas inexistentas qu'an de redireccions puntant cap a elas, vejatz [[{{#special:BrokenRedirects}}|la lista de las redireccions copadas]].",
        "wantedpages-badtitle": "Títol invalid dins los resultats : $1",
        "wantedfiles": "Fichièrs desirats",
        "wantedfiletext-cat": "Los fichièrs seguents son utilizats, mas existisson pas localament. Se se tròban sus un depaus partejat, pòdon èsser listats aicí, mentre que sián, de fait, ja disponibles. Totes aqueles falses positius seràn <del>raiats</del>. Amai, las paginas qu'intègran de fichièrs qu'existisson pas son repertoriadas dins [[:$1]].",
        "protectedpages-unknown-timestamp": "Desconegut",
        "protectedpages-unknown-performer": "Utilizaire desconegut",
        "protectedtitles": "Títols protegits",
-       "protectedtitles-summary": "Aquò es una lista dei títols de pagina que son a l'ora d'ara protegits còntra la creacion. Per una lista dei paginas existentas que son protegidas, veire [[{{#special:ProtectedPages}}|{{int:protectedpages}}]].",
+       "protectedtitles-summary": "Aquò es una lista dels títols de pagina que son a l'ora d'ara protegits contra la creacion. Per una lista de las paginas existentas que son protegidas, veire [[{{#special:ProtectedPages}}|{{int:protectedpages}}]].",
        "protectedtitlesempty": "Cap de títol es pas actualament protegit amb aquestes paramètres.",
        "protectedtitles-submit": "Afichar los títols",
        "listusers": "Lista dels participants",
        "apisandbox-submit": "Far la demanda",
        "apisandbox-reset": "Escafar",
        "apisandbox-retry": "Ensajar tornarmai",
+       "apisandbox-load-error": "Una error s'es produita pendent lo cargament de las informacions del modul \"$1\" de l'API : $2",
+       "apisandbox-no-parameters": "Aqueste modul API a pas cap de paramètre.",
        "apisandbox-helpurls": "Ligams d'ajuda",
        "apisandbox-examples": "Exemples",
        "apisandbox-dynamic-parameters": "Paramètres suplementaris",
        "apisandbox-dynamic-parameters-add-label": "Apondon del paramètre",
        "apisandbox-dynamic-parameters-add-placeholder": "Nom del paramètre",
+       "apisandbox-dynamic-error-exists": "Existís ja un paramètre nomenat \"$1\"",
        "apisandbox-deprecated-parameters": "Paramètres obsolèts",
        "apisandbox-fetch-token": "Auto-emplenatge del geton",
-       "apisandbox-submit-invalid-fields-title": "De camps son invalides",
+       "apisandbox-submit-invalid-fields-title": "Certans camps son invalids",
+       "apisandbox-submit-invalid-fields-message": "Corregissètz los camps indicats e ensajatz tornamai.",
        "apisandbox-results": "Resultats",
        "apisandbox-sending-request": "Mandadís de la requèsta a l'API...",
        "apisandbox-loading-results": "Recepcion dels resultats de l'API...",
        "apisandbox-request-url-label": "Requèsta URL :",
        "apisandbox-request-json-label": "Demandar de JSON :",
        "apisandbox-request-time": "Durada de la demanda : {{PLURAL:$1|$1 ms}}",
+       "apisandbox-alert-page": "Los camps d'aquesta pagina son pas valids.",
+       "apisandbox-alert-field": "La valor d'aqueste camp es pas valida.",
        "apisandbox-continue": "Contunhar",
        "apisandbox-continue-clear": "Escafar",
+       "apisandbox-param-limit": "Entrar <kbd>max</kbd> per utilizar lo limit maximal.",
        "apisandbox-multivalue-all-namespaces": "$1 (totes los espacis de noms)",
        "apisandbox-multivalue-all-values": "$1 (totas las valors)",
        "booksources": "Obratges de referéncia",
        "activeusers-count": "$1 {{PLURAL:$1|accion|accions}} al moment {{PLURAL:$3|del darrièr jorn|dels $3 darrièrs jorns}}",
        "activeusers-from": "Afichar los utilizaires dempuèi :",
        "activeusers-noresult": "Cap d'utilizaire pas trobat.",
-       "activeusers-submit": "Mostrar leis utilizaires actius",
+       "activeusers-submit": "Afichar los utilizaires actius",
        "listgrouprights": "Dreits dels gropes d'utilizaires",
        "listgrouprights-summary": "Aquesta pagina conten una lista de gropes definits sus aqueste wiki e mai los dreits d'accès qu'i son associats.\nI pòt aver [[{{MediaWiki:Listgrouprights-helppage}}|d'entresenhas complementàrias]] a prepaus dels dreits.",
        "listgrouprights-key": "Legenda :\n*<span class=\"listgrouprights-granted\">Dreit autrejat</span>\n*<span class=\"listgrouprights-revoked\">Dreit revocat</span>",
        "emailusername": "Nom d'utilizaire :",
        "emailusernamesubmit": "Sometre",
        "email-legend": "Mandar un corrièr electronic a un autre utilizaire de {{SITENAME}}",
-       "emailfrom": "Expeditor :",
+       "emailfrom": "De :",
        "emailto": "Destinatari :",
        "emailsubject": "Subjècte :",
        "emailmessage": "Messatge :",
        "emailccsubject": "Còpia de vòstre messatge a $1 : $2",
        "emailsent": "Messatge mandat",
        "emailsenttext": "Vòstre messatge es estat mandat.",
-       "emailuserfooter": "Aqueste corrièr electronic es estat mandat per « $1 » a « $2 » per la foncion « {{int:emailuser}} » sus {{SITENAME}}.",
+       "emailuserfooter": "Aqueste corrièr electronic es {{GENDER:$1|estat mandat}} per « $1 » a « {{GENDER:$2|$2}} » per la foncion « {{int:emailuser}} » sus {{SITENAME}}. Se {{GENDER:$2|respondètz}} a aqueste corrièl, {{GENDER:$2|vòstre}} corrièl serà mandat dirèctament a l’{{GENDER:$1|emeteire inicial}}, en {{GENDER:$1|li}} mencionant {{GENDER:$2|vòstra}} adreça de courrièl .",
        "usermessage-summary": "A daissat un messatge de sistèma.",
        "usermessage-editor": "Messatgièr del sistèma",
        "watchlist": "Lista de seguiment",
        "changecontentmodel-model-label": "Novèl modèl de contengut",
        "changecontentmodel-reason-label": "Motiu :",
        "changecontentmodel-submit": "Modificar",
+       "changecontentmodel-emptymodels-title": "Cap de modèl de contengut pas disponible",
        "logentry-contentmodel-change-revertlink": "restablir",
        "logentry-contentmodel-change-revert": "restablir",
        "protectlogpage": "Istoric de las proteccions",
        "modifiedarticleprotection": "a modificat lo nivèl de proteccion de « [[$1]] »",
        "unprotectedarticle": "a suprimit la proteccion de « [[$1]] »",
        "movedarticleprotection": "a desplaçat los paramètres de proteccion dempuèi « [[$2]] » cap a « [[$1]] »",
+       "protectedarticle-comment": "{{GENDER:$2|A protegit}} « [[$1]] »",
+       "modifiedarticleprotection-comment": "{{GENDER:$2|A cambiat lo nivèl de proteccion}} per « [[$1]] »",
+       "unprotectedarticle-comment": "{{GENDER:$2|A suprimit la proteccion}} de « [[$1]] »",
        "protect-title": "Cambiar lo nivèl de proteccion de « $1 »",
        "protect-title-notallowed": "Veire lo nivèl de proteccion de « $1 »",
        "prot_1movedto2": "a renomenat [[$1]] en [[$2]]",
        "protect-locked-blocked": "Podètz pas modificar los nivèls de proteccion tant que sètz bloca{{GENDER:||t|ada}}.. Vaquí los reglatges actuals de la pagina <strong>$1</strong> :",
        "protect-locked-dblock": "Lo nivèl de proteccion pòt pas èsser modificat perque la basa de donadas es verrolhada.\nVaquí los reglatges actuals de la pagina <strong>$1</strong> :",
        "protect-locked-access": "Avètz pas los dreits necessaris per modificar los nivèls de proteccion de las paginas.\nVaquí los reglatges actuals de la pagina <strong>$1</strong> :",
-       "protect-cascadeon": "Aquesta pagina es protegida perque es inclusa dins {{PLURAL:$1|la pagina seguenta qu'es estada protegida|las paginas seguentas que son estadas protegidas}}, amb l’opcion « proteccion en cascada » activada. Podètz cambiar lo nivèl de proteccion d'aquesta pagina sens qu'aquò afècte la proteccion en cascada.",
+       "protect-cascadeon": "Aquesta pagina es protegida perque es transclusa dins {{PLURAL:$1|la pagina seguenta qu'es estada protegida|las paginas seguentas que son estadas protegidas}}, amb l’opcion « proteccion en cascada » d'activada.\nPodètz cambiar lo nivèl de proteccion d'aquesta pagina sens qu'aquò afècte la proteccion en cascada.",
        "protect-default": "Autorizar totes los utilizaires",
        "protect-fallback": "Autorizar unicament los utilizaires amb lo dreit « $1 »",
        "protect-level-autoconfirmed": "Autorizar unicament los utilizaires autoconfirmats",
        "unblock": "Desblocar un utilizaire",
        "blockip": "Blocar l’{{GENDER:$1|utilizaire|utilizaira}}",
        "blockip-legend": "Blocar en escritura",
-       "blockiptext": "Utilizatz lo formulari çaijós per blocar l'accès a las modificacions a partir d'una adreça IP especifica o d'un nom d'utilizaire.\nUna tala mesura deu pas èsser presa pas que per empachar lo vandalisme e en acòrdi amb las [[{{MediaWiki:Policy-url}}|règlas intèrnas]].\nDonatz çaijós un motiu precís (per exemple en citant las paginas que son estadas vandalizadas).",
+       "blockiptext": "Utilizatz lo formulari çaijós per blocar las temptativas de modificacion a partir d'una adreça IP especifica o d'un nom d'utilizaire.\nUna tala mesura deuriá pas èsser presa pas que per empachar lo vandalisme e en acòrd amb las [[{{MediaWiki:Policy-url}}|règlas intèrnas]].\nDonatz çaijós un motiu precís (per exemple en citant las paginas que son estadas vandalizadas).\nPodètz blocar de plajas d’adreças IP en utilizant la sintaxi [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR] ; la mai granda plaja autorizada es /$1 per IP v4 e /$2 per IP v6.",
        "ipaddressorusername": "Adreça IP o nom d’utilizaire :",
        "ipbexpiry": "Durada del blocatge :",
        "ipbreason": "Motiu :",
        "databasenotlocked": "La basa de donadas es pas verrolhada.",
        "lockedbyandtime": "(per $1 lo $2 a $3)",
        "move-page": "Renomenar $1",
-       "move-page-legend": "Tornar nomenar una pagina",
-       "movepagetext": "Utilizatz lo formulari çaijós per tornar nomenar una pagina, en desplaçant tot son istoric cap al nom novèl. Lo títol ancian vendrà una pagina de redireccion cap al títol novèl. Podètz metre a jorn automaticament las redireccions actualas que puntan cap al títol original. Se causissètz de lo far pas, asseguratz-vos de verificar tota [[Special:DoubleRedirects|redireccion dobla]] o [[Special:BrokenRedirects|redireccion copada]]. Avètz la responsabilitat de vos assegurar que los ligams contunhen de puntar cap a lor destinacion supausada.\n\nNotatz que la pagina serà '''pas''' renomada s'existís ja una pagina amb lo novèl títol, levat se aquesta darrièra a un istoric de modificacions verge e es una simpla redireccion. Aquò permet de renomenar una pagina cap a sa posicion d'origina se lo desplaçament s'avera erronèu.\n\n'''ATENCION !'''\nAquò pòt provocar un cambiament radical e imprevist per una pagina consultada frequentament ; asseguratz-vos de n'aver comprés las consequéncias abans de contunhar.",
-       "movepagetalktext": "La pagina de discussion associada, se presenta, serà automaticament desplaçada amb ''' levat se :'''\n*Desplaçatz una pagina cap a un autre espaci,\n*Una pagina de discussion ja existís amb lo nom novèl, o\n*Avètz deseleccionat lo boton çaijós.\n\nDins aqueste cas, vos caldrà desplaçar o fusionar la pagina manualament se o volètz.",
+       "move-page-legend": "Renomenar una pagina",
+       "movepagetext": "Utilizatz lo formulari çaijós per renomenar una pagina, en desplaçant tot son istoric cap al nom novèl. Lo títol ancian vendrà una pagina de redireccion cap al títol novèl.\nPodètz metre a jorn automaticament las redireccions actualas que puntan cap al títol original.\nSe causissètz de lo far pas, asseguratz-vos de verificar tota [[Special:DoubleRedirects|redireccion dobla]] o [[Special:BrokenRedirects|redireccion copada]]. Avètz la responsabilitat de vos assegurar que los ligams contunhan de puntar cap a lor destinacion supausada.\n\nNotatz que la pagina serà <strong>pas</strong>  renomada s'existís ja una pagina amb lo novèl títol, levat se aquesta darrièra a un istoric de modificacions verge e es una simpla redireccion. \nAquò significa que podètz renomenar una pagina cap a sa posicion d'origina se vos sètz enganat e que podètz pas espotir una pagina existenta.\n\n<strong>Atencion !</strong>\nAquò pòt provocar un cambiament radical e imprevist per una pagina consultada frequentament ; asseguratz-vos qu'avètz comprés las consequéncias de vòstra accion abans de contunhar.",
+       "movepagetalktext": "Se marcatz aquesta casa, la pagina de discussion associada serà automaticament renomenada levat se una pagina de discussion, non voida, existís ja amb aqueste nom novèl.\n\nDins aqueste cas, vos caldrà renomenar o fusionar la pagina manualament se o volètz.",
        "moveuserpage-warning": "'''Atencion :''' Sètz a mand de tornar nomenar una pagina d’utilizaire. Notatz que sola la pagina serà renomenada e que l’utilizaire '''ne''' serà '''pas''' renomenat.",
        "movenologintext": "Per poder renomenar una pagina, vos cal èsser [[Special:UserLogin|indentific{{GENDER:||t|da)}}]]\nen tant qu'utilizaire enregistrat amb pro d'ancianetat.",
        "movenotallowed": "Avètz pas la permission de renomenar de paginas.",
        "cant-move-user-page": "Avètz pas la permission de renomenar las paginas principalas d'utilizaires.",
        "cant-move-to-user-page": "Avètz pas la permission de tornar nomenar una pagina cap a una pagina d'utilizaire (a l'excepcion d'una sospagina).",
        "cant-move-category-page": "Avètz pas la permission de renomenar las paginas de categorias.",
-       "cant-move-to-category-page": "Avètz pas lei drechs necessaris per desplaçar una pagina vèrs una categoria",
-       "cant-move-subpages": "Avètz pas lei drechs necessaris per desplaçar de sota-paginas.",
-       "namespace-nosubpages": "Lo nom d'espaci $1 autoriza pas lei sota-paginas.",
+       "cant-move-to-category-page": "Avètz pas lo dreit de renomenar una pagina cap a una pagina de categoria.",
+       "cant-move-subpages": "Avètz pas lo dreit de renomenar de sospaginas.",
+       "namespace-nosubpages": "L’espaci de noms « $1 » autoriza pas las sospaginas.",
        "newtitle": "Títol novèl :",
        "move-watch": "Seguir aquesta pagina",
        "movepagebtn": "Renomenar l'article",
        "articleexists": "Existís ja un article que pòrta aqueste títol, o lo títol qu'avètz causit es pas valid.\nCausissètz-ne un autre.",
        "cantmove-titleprotected": "Avètz pas la possibilitat de desplaçar una pagina cap a aqueste emplaçament perque lo títol es estat protegit a la creacion.",
        "movetalk": "Renomenar tanben la pagina de discussion associada",
-       "move-subpages": "Tornar nomenar las sospaginas (fins a $1 paginas)",
-       "move-talk-subpages": "Tornar nomenar las sospaginas de la pagina de discussion (fins a $1 paginas)",
+       "move-subpages": "Renomenar las sospaginas (maximum $1)",
+       "move-talk-subpages": "Renomenar las sospaginas de la pagina de discussion (maximum $1 paginas)",
        "movepage-page-exists": "La pagina $1 existís ja e pòt pas èsser espotida automaticament.",
        "movepage-page-moved": "La pagina $1 es estada renomenada en $2.",
        "movepage-page-unmoved": "La pagina $1 pòt èsser renomenada en $2.",
        "delete_and_move_confirm": "Òc, accèpti de suprimir la pagina de destinacion per permetre lo cambiament de nom.",
        "delete_and_move_reason": "Pagina suprimida per permetre lo cambiament de nom dempuèi « [[$1]] »",
        "selfmove": "Los títols d’origina e de destinacion son los meteisses : impossible de tornar nomenar una pagina sus ela-meteissa.",
-       "immobile-source-namespace": "Podètz pas tornar nomenar de paginas dins l'espaci de noms « $1 »",
+       "immobile-source-namespace": "Podètz pas renomenar las paginas dins l'espaci de noms « $1 »",
        "immobile-target-namespace": "Podètz pas desplaçar de paginas cap a l'espaci de noms « $1 »",
        "immobile-target-namespace-iw": "Los ligams interwikis son pas una cibla valida pels cambiaments de nom.",
-       "immobile-source-page": "Aquesta pagina se pòt pas tornar nomenar.",
+       "immobile-source-page": "Aquesta pagina se pòt pas renomenar.",
        "immobile-target-page": "Es pas possible de desplaçar la pagina cap a aqueste títol.",
        "imagenocrossnamespace": "Pòt pas desplaçar un imatge cap a un espaci de nomenatge que siá pas un imatge.",
        "nonfile-cannot-move-to-file": "Impossible de renomenar quicòm mai qu'un fichièr cap a l'espaci de noms fichièr.",
        "move-leave-redirect": "Daissar una redireccion darrièr",
        "protectedpagemovewarning": "'''ATENCION:''' Aquesta pagina es estada protegida per que sonque los utilizaires qu'an los dreits d'administrators la pòscan renomenar. La darrièra entrada del jornal es afichada çaijós per referéncia :",
        "semiprotectedpagemovewarning": "'''Nòta :''' Aquesta pagina es estada blocada per que sonque los utilizaires enregistrats la pòscan tornar nomenar. La darrièra entrada del jornal es afichada çaijós per referéncia :",
-       "move-over-sharedrepo": "== Lo fichièr existís ==\n[[:$1]] existís ja sus un depaus partejat. Tornar nomenar aqueste fichièr farà lo fichièr sul depaus partatge inaccessible.",
+       "move-over-sharedrepo": "[[:$1]] existís ja sus un depaus partejat. Renomenar aqueste fichièr rendrà lo fichièr sul depaus partejat inaccessible.",
        "file-exists-sharedrepo": "Lo nom causit es ja utilizat per un fichièr sus un depaus partejat.\nCausissètz un autre nom.",
        "export": "Exportar de paginas",
        "exporttext": "Podètz exportar en XML lo tèxte e l’istoric d’una pagina o d’un ensemble de paginas; lo resultat pòt alara èsser importat dins un autre wiki que fonciona amb lo logicial MediaWiki.\n\nPer exportar de paginas, entratz lors títols dins la bóstia de tèxte çaijós, un títol per linha, e seleccionatz s'o desiratz o pas la version actuala amb totas las versions ancianas, amb la pagina d’istoric, o simplament la pagina actuala amb d'informacions sus la darrièra modificacion.\n\nDins aqueste darrièr cas, podètz tanben utilizar un ligam, coma [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] per la pagina [[{{MediaWiki:Mainpage}}]].",
        "export-download": "Salvar en tant que fichièr",
        "export-templates": "Enclure los modèls",
        "export-pagelinks": "Enclure las paginas ligadas a una prigondor de :",
-       "export-manual": "Ajustar de paginas manualament :",
+       "export-manual": "Apondre de paginas manualament :",
        "allmessages": "Lista dels messatges del sistèma",
        "allmessagesname": "Nom del camp",
        "allmessagesdefault": "Messatge per defaut",
        "thumbnail_image-missing": "Lo fichièr seguent es introbable : $1",
        "import": "Importar de paginas",
        "importinterwiki": "Importar a partir d'un autre wiki",
-       "import-interwiki-text": "Seleccionatz un wiki e un títol de pagina d'importar.\nLas datas de las versions e los noms dels editors seràn preservats.\nTotas las accions d’importacion interwiki son conservadas dins lo [[Special:Log/import|jornal d’impòrt]].",
+       "import-interwiki-text": "Seleccionatz un wiki e un títol de pagina d'importar.\nLas datas de las versions e los noms dels contributors seràn preservats.\nTotas las accions d’importacion a partir d'autres wikis son conservadas dins l' [[Special:Log/import|istoric de las importacions]].",
        "import-interwiki-sourcewiki": "Wiki font :",
        "import-interwiki-sourcepage": "Pagina font :",
        "import-interwiki-history": "Copiar totas las versions de l'istoric d'aquesta pagina",
        "importcantopen": "Impossible de dobrir lo fichièr d'importar",
        "importbadinterwiki": "Ligam interwiki marrit",
        "importsuccess": "L'impòrt a capitat !",
-       "importnosources": "Cap de font interwiki es pas estada definida e la còpia dirècta d’istoric es desactivada.",
+       "importnosources": "Cap de font wiki es pas estada definida e l'impòrt dirècte d’istorics es desactivat.",
        "importnofile": "Cap de fichièr es pas estat importat.",
        "importuploaderrorsize": "Lo telecargament del fichièr d'importar a pas capitat. Sa talha es mai granda que la autorizada.",
        "importuploaderrorpartial": "Lo telecargament del fichièr d'importar a pas capitat. Aqueste o es pas estat que parcialament.",
        "tooltip-p-logo": "Pagina principala",
        "tooltip-n-mainpage": "Visitatz la pagina principala",
        "tooltip-n-mainpage-description": "Anar a l’acuèlh",
-       "tooltip-n-portal": "A prepaus del projècte",
+       "tooltip-n-portal": "A prepaus del projècte, çò que podètz far, ont trobar d'informacions",
        "tooltip-n-currentevents": "Trobar d'entresenhas suls eveniments actuals",
        "tooltip-n-recentchanges": "Lista dels darrièrs cambiaments sul wiki",
        "tooltip-n-randompage": "Afichar una pagina a l'azard",
        "pageinfo-edits": "Nombre total de modificacions",
        "pageinfo-authors": "Nombre total d'autors distinctes",
        "pageinfo-recent-edits": "Nombre de modificacions recentas (dins los darrièrs $1)",
-       "pageinfo-recent-authors": "Nombre d'autors distinctes recents",
+       "pageinfo-recent-authors": "Nombre d'autors distintes recents",
        "pageinfo-magic-words": "{{PLURAL:$1|Mot magic|Mots magics}} ($1)",
        "pageinfo-hidden-categories": "{{PLURAL:$1|Categoria amagada|Categorias amagadas}} ($1)",
        "pageinfo-templates": "{{PLURAL:$1|Modèl inclús|Modèls incluses}} ($1)",
        "scarytranscludefailed-httpstatus": "[Fracàs de la recuperacion del modèl per  $1 : HTTP  $2 ]",
        "scarytranscludetoolong": "[L’URL es tròp longa]",
        "deletedwhileediting": "'''Atencion''' : aquesta pagina es estada suprimida aprèp qu'avètz començat de la modificar !",
-       "confirmrecreate": "L'utilizaire [[User:$1|$1]] ([[User talk:$1|talk]]) a suprimit aquesta pagina, alara que l'aviatz començat d'editar, pel motiu seguent:\n: ''$2''\nConfirmatz que desiratz tornar crear aqueste article.",
-       "confirmrecreate-noreason": "L'utilizaire [[User:$1|$1]] ([[User talk:$1|Discussion]]) a suprimit aquesta pagina, mentre qu'aviatz començat de la modificar. Confirmatz que desiratz tornar crear aquesta pagina.",
+       "confirmrecreate": "L'utilizair{{GENDER:$1|e|a}} [[User:$1|$1]] ([[User talk:$1|Discussion]]) a suprimit aquesta pagina, mentre qu'aviatz començat de l'editar, pel motiu seguent:\n: <em>$2</em>\nConfirmatz que sètz segur que volètz tornar crear aquesta pagina.",
+       "confirmrecreate-noreason": "L'utilizair{{GENDER:$1|e|a}}[[User:$1|$1]] ([[User talk:$1|Discussion]]) a suprimit aquesta pagina, mentre qu'aviatz començat de la modificar. Confirmatz que sètz segur que volètz tornar crear aquesta pagina.",
        "recreate": "Tornar crear",
        "confirm_purge_button": "Confirmar",
        "confirm-purge-top": "Volètz refrescar aquesta pagina (purgar l'escondedor) ?",
        "htmlform-datetime-placeholder": "AAAA-MM-JJ HH:MM:SS",
        "htmlform-title-not-exists": "$1 existís pas.",
        "htmlform-user-not-exists": "<strong>$1</strong> existís pas.",
-       "htmlform-user-not-valid": "<strong>$1</strong> es pas un nom d'utilizaire valide.",
+       "htmlform-user-not-valid": "<strong>$1</strong> es pas un nom d'utilizaire valid.",
        "logentry-delete-delete": "$1 {{GENDER:$2|a suprimit}} la pagina $3",
+       "logentry-delete-delete_redir": "$1 {{GENDER:$2|suprimit}} redirigit cap a $3 per espotiment.",
        "logentry-delete-restore": "$1 {{GENDER:$2|a restablit}} la pagina $3",
        "logentry-delete-event": "$1 {{GENDER:$2|a modificat}} la visibilitat {{PLURAL:$5|d'un eveniment del jornal|de $5 eveniments del jornal}} sus $3 : $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|a modificat}} la visibilitat {{PLURAL:$5|d'una revision|de $5 revisions}} sus la pagina $3 : $4",
        "pagelang-use-default": "Utilizar la lenga per defaut",
        "pagelang-select-lang": "Seleccionar la lenga",
        "pagelang-reason": "Motiu",
-       "pagelang-submit": "Validar",
+       "pagelang-submit": "Mandar",
        "pagelang-nonexistent-page": "La pagina $1 existís pas.",
        "right-pagelang": "Cambiar la lenga de la pagina",
        "action-pagelang": "cambiar la lenga de la pagina",
        "special-characters-title-minus": "signe mens",
        "mw-widgets-dateinput-no-date": "Cap de data pas seleccionada",
        "mw-widgets-mediasearch-input-placeholder": "Recercar de mèdias",
-       "mw-widgets-mediasearch-noresults": "Ges de resultat trobat",
-       "mw-widgets-categoryselector-add-category-placeholder": "Ajustar una categoria...",
-       "sessionprovider-generic": "$1 sessions",
+       "mw-widgets-mediasearch-noresults": "Cap de resultat pas trobat.",
+       "mw-widgets-categoryselector-add-category-placeholder": "Apondre una categoria...",
+       "mw-widgets-usersmultiselect-placeholder": "Apondre mai...",
+       "sessionprovider-generic": "sessions $1",
        "sessionprovider-mediawiki-session-cookiesessionprovider": "sessions basadas sus de cookies",
-       "sessionprovider-nocookies": "Lei cookies son benlèu desactivats. Verificatz que lei cookies siegan ben activat e tornatz assaiar.",
+       "sessionprovider-nocookies": "Es possible que los testimònis (''cookies'') sián desactivats. Asseguratz-vos qu'avètz activat los testimònis e recomençatz.",
        "randomrootpage": "Pagina raiç aleatòria",
-       "log-action-filter-block": "Tipe d'accion :",
-       "log-action-filter-delete": "Tipe d'accion :",
+       "log-action-filter-block": "Tipe de blocatge :",
+       "log-action-filter-delete": "Tipe de supression :",
        "log-action-filter-import": "Tipe d'importacion :",
-       "log-action-filter-managetags": "Tipe d'accion :",
+       "log-action-filter-managetags": "Tipe d'accion de gestion de las etiquetas :",
        "log-action-filter-move": "Tipe de desplaçament :",
-       "log-action-filter-newusers": "Tipe de creacion :",
+       "log-action-filter-newusers": "Tipe de creacion de compte :",
        "log-action-filter-patrol": "Tipe de patrolha :",
-       "log-action-filter-protect": "Tipe d'accion :",
+       "log-action-filter-protect": "Tipe de proteccion :",
        "log-action-filter-rights": "Tipe de cambiament de dreits :",
        "log-action-filter-suppress": "Tipe de supression :",
-       "log-action-filter-upload": "Tipe de telecargament :",
+       "log-action-filter-upload": "Tipe de mandadís :",
        "log-action-filter-all": "Tot",
        "log-action-filter-block-block": "Blocatge",
        "log-action-filter-block-reblock": "Modificacion de blocatge",
        "log-action-filter-block-unblock": "Desblocar",
-       "log-action-filter-delete-delete": "Escafament de pagina",
-       "log-action-filter-delete-restore": "Restauracion de pagina",
+       "log-action-filter-delete-delete": "Supression de paginas",
+       "log-action-filter-delete-restore": "Restabliment de pagina",
        "log-action-filter-import-interwiki": "Impòrt transwiki",
-       "log-action-filter-managetags-create": "Creacion d'etiqueta",
-       "log-action-filter-managetags-delete": "Supression d'etiqueta",
-       "log-action-filter-managetags-activate": "Activacion d'etiqueta",
-       "log-action-filter-managetags-deactivate": "Desactivacion d'etiqueta",
+       "log-action-filter-managetags-create": "Creacion de balisa",
+       "log-action-filter-managetags-delete": "Supression de balisa",
+       "log-action-filter-managetags-activate": "Activacion de l'etiqueta",
+       "log-action-filter-managetags-deactivate": "Desactivacion de l'etiqueta",
        "log-action-filter-newusers-create": "Creacion per un utilizaire anonim",
        "log-action-filter-newusers-create2": "Creacion per un utilizaire enregistrat",
        "log-action-filter-newusers-autocreate": "Creacion automatica",
-       "log-action-filter-newusers-byemail": "Creacion amb un senhau mandat per corrier electronic",
+       "log-action-filter-newusers-byemail": "Creacion amb un senhal mandat per corrièr electronic",
        "log-action-filter-patrol-patrol": "Patrolha manuala",
        "log-action-filter-patrol-autopatrol": "Patrolha automatica",
        "log-action-filter-protect-protect": "Proteccion",
-       "log-action-filter-protect-modify": "Modificacion de proteccion",
+       "log-action-filter-protect-modify": "Modificacion de la proteccion",
        "log-action-filter-protect-unprotect": "Desproteccion",
        "log-action-filter-protect-move_prot": "Proteccion de renomenatge",
-       "log-action-filter-rights-rights": "Cambiament manuau",
+       "log-action-filter-rights-rights": "Cambiament manual",
        "log-action-filter-rights-autopromote": "Cambiament automatic",
        "log-action-filter-suppress-event": "Supression de jornal",
        "log-action-filter-suppress-revision": "Supression de revision",
        "log-action-filter-suppress-delete": "Supression de pagina",
-       "log-action-filter-upload-upload": "Telecargament novèu",
-       "log-action-filter-upload-overwrite": "Retelecargament",
-       "authmanager-create-disabled": "La creacion de còmptes es blocada.",
-       "authmanager-authplugin-setpass-bad-domain": "Domeni invalide",
-       "authmanager-autocreate-noperm": "La creacion automatica de còmptes es blocada.",
-       "authmanager-password-help": "Senhau per autentificacion.",
-       "authmanager-domain-help": "Domeni per autentificacion extèrna.",
-       "authmanager-retype-help": "Mercé de confiermar vòstre senhau.",
+       "log-action-filter-upload-upload": "Mandadís novèl",
+       "log-action-filter-upload-overwrite": "Tornar mandar",
+       "authmanager-authn-autocreate-failed": "La creacion automatica d’un compte local a fracassat : $1",
+       "authmanager-create-disabled": "La creacion de compte es desactivada.",
+       "authmanager-authplugin-setpass-bad-domain": "Domeni invalid.",
+       "authmanager-autocreate-noperm": "La creacion automatica de compte es pas autorizada.",
+       "authmanager-password-help": "Senhal per l'autentificacion.",
+       "authmanager-domain-help": "Domeni per l'autentificacion extèrna.",
+       "authmanager-retype-help": "Senhal un còp de mai per confirmacion.",
        "authmanager-email-label": "Corrièr electronic",
        "authmanager-email-help": "Adreça de corrièr electronic",
        "authmanager-realname-label": "Nom vertadièr",
        "authmanager-realname-help": "Nom real de l'utilizaire",
-       "authmanager-provider-password": "Autentificacion ambé senhau",
-       "authmanager-provider-temporarypassword": "Senhau provisòri",
-       "authprovider-confirmlink-message": "Segon lei vòstreis assais recents de connexion, lei còmptes seguents pòdon èsser liats vèrs lo vòstre còmpte wiki. Lei liar permet de se connectar amb aqueleis còmptes. Mercé de seleccionar lei còmptes de liar.",
-       "authprovider-confirmlink-request-label": "Còmptes de liar",
-       "authprovider-confirmlink-success-line": "$1 : operacion capitada, lei còmptes son estats liats.",
-       "authprovider-confirmlink-failed": "La temptativa de liar lei còmptes a pas capitat : $1",
+       "authmanager-provider-password": "Autentificacion amb senhal",
+       "authmanager-provider-temporarypassword": "Senhal provisòri",
+       "authprovider-confirmlink-message": "D’aprèp vòstras darrièras temtativas de connexion, los comptes seguents pòson èsser ligats a vòstre compte wiki. Los ligar vos permetrà de vos connectar via aquestes comptes. Seleccionatz los que devon èsser ligats.",
+       "authprovider-confirmlink-request-label": "Comptes que devon èsser ligats",
+       "authprovider-confirmlink-success-line": "$1 : Ligats amb succès.",
+       "authprovider-confirmlink-failed": "La ligason del compte a pas plan capitat : $1",
        "authprovider-resetpass-skip-label": "Sautar",
        "authform-newtoken": "Geton mancant. $1",
        "authform-notoken": "Geton mancant",
        "authform-wrongtoken": "Marrit geton",
-       "specialpage-securitylevel-not-allowed-title": "Pas autorizat",
+       "specialpage-securitylevel-not-allowed-title": "Interdit",
        "changecredentials": "Modificar las informacions d’identificacion",
+       "removecredentials": "Suprimir las informacions d'identificacion",
+       "removecredentials-submit": "Suprimir las informacions d'identificacion",
+       "credentialsform-provider": "Tipe d’informacion d’identificacion :",
        "credentialsform-account": "Nom de compte :",
-       "linkaccounts": "Liar lei còmptes",
-       "linkaccounts-success-text": "Lo còmpte èra estat liat.",
-       "linkaccounts-submit": "Liar lei còmptes",
+       "cannotlink-no-provider-title": "I a pas de comptes que pòdon èsser ligats",
+       "cannotlink-no-provider": "I a pas de compte que pòsca èsser ligat.",
+       "linkaccounts": "Ligar los comptes",
+       "linkaccounts-success-text": "Lo compte es estat ligat.",
+       "linkaccounts-submit": "Ligar los comptes",
        "restrictionsfield-badip": "Adreça IP o plaja invalida : $1",
-       "revid": "Revision $1",
-       "pageid": "Pagina ID $1"
+       "revid": "version $1",
+       "pageid": "ID de pagina $1"
 }
index 4859f81..d287f5a 100644 (file)
        "userrights-user-editname": "Wprowadź nazwę użytkownika:",
        "editusergroup": "Wczytaj grupy użytkownika",
        "editinguser": "Zmiana uprawnień {{GENDER:$1|użytkownika|użytkowniczki}} <strong>[[User:$1|$1]]</strong> $2",
-       "viewinguserrights": "Przeglądanie grup do których należy {{GENDER:$1|użytkownik|użytkowniczka}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Przeglądanie grup, do których należy {{GENDER:$1|użytkownik|użytkowniczka}} <strong>[[User:$1|$1]]</strong> $2",
        "userrights-editusergroup": "Edytuj grupy {{GENDER:$1|użytkownika|użytkowniczki}}",
        "userrights-viewusergroup": "Przeglądanie grup {{GENDER:$1|użytkownika|użytkowniczki}}",
        "saveusergroups": "Zapisz grupy {{GENDER:$1|użytkownika|użytkowniczki}}",
        "uploaded-setting-handler-svg": "SVG ustawiające atrybut \"handler\" z zdalnymi elementami/danymi/skryptami są blokowane. Znaleziono <code>$1=\"$2\"</code> w przesyłanym pliku SVG.",
        "uploaded-remote-url-svg": "SVG, które ustawiają w dowolnym atrybucie stylu zdalne URL, są blokowane. Znaleziono <code>$1=\"$2\"</code> w przesyłanym pliku SVG.",
        "uploaded-image-filter-svg": "Znaleziono filtr obrazu zawierający URL: <code>&lt;$1 $2=\"$3\"&gt;</code> w przesyłanym pliku SVG.",
-       "uploadscriptednamespace": "Ten plik SVG zawiera niedozwoloną przestrzeń nazw '<nowiki>$1</nowiki>'",
+       "uploadscriptednamespace": "Ten plik SVG zawiera niedozwoloną przestrzeń nazw „<nowiki>$1</nowiki>”.",
        "uploadinvalidxml": "Nie udało się przeanalizować XML w załadowanym pliku.",
        "uploadvirus": "W pliku jest wirus! Szczegóły: $1",
        "uploadjava": "Ten plik zawiera deklarację klasy Java skompresowaną ZIP.\nPrzesyłanie plików Java nie jest dozwolone, ponieważ mogłoby zostać użyte do obchodzenia zabezpieczeń.",
        "confirmemail_invalidated": "Potwierdzenie adresu e‐mail zostało anulowane",
        "invalidateemail": "Anulowanie potwierdzenia adresu e‐mail",
        "notificationemail_subject_changed": "Został zmieniony zarejestrowany adres e-mail na {{SITENAME}}",
-       "notificationemail_subject_removed": "{{SITENAME}} zarejestrowany adres e-mail został usunięty",
+       "notificationemail_subject_removed": "Zarejestrowany adres e-mail w {{GRAMMAR:MS.lp|SITENAME}} został usunięty",
        "scarytranscludedisabled": "[Transkluzja przez interwiki jest wyłączona]",
        "scarytranscludefailed": "[Pobranie szablonu dla $1 nie powiodło się]",
        "scarytranscludefailed-httpstatus": "[Pobranie szablonu dla $1 nie powiodło się: HTTP $2]",
index 1204519..7fb2bb2 100644 (file)
        "searcharticle": "သိုပ်ႇၵႂႃႇ",
        "history": "ပိုၼ်းၼႃႈလိၵ်ႈ",
        "history_short": "ပိုၼ်း",
+       "history_small": "ပိုၼ်း",
        "updatedmarker": "ပဵၼ်ဢၢပ်ႉတိတ်ႉဝႆႉ ၸဵမ်မိူဝ်ႈၶႃႈၵႂႃႇဢႅဝ်ႇၵမ်းလိုၼ်းသုတ်း",
        "printableversion": "တွၼ်ႈတႃႇဢိတ်ႇဢွၵ်ႇ",
        "permalink": "ႁဵင်းၵွင်ႉ မၼ်ႈၵိုမ်း",
        "views": "လူတူၺ်း",
        "toolbox": "ၶိူင်ႈၵမ်ႉၵႅမ်",
        "tool-link-userrights": "လႅၵ်ႈလၢႆႈ {{GENDER:$1|ၽူႈၸႂ်ႉတိုဝ်း}} ၸုမ်း",
+       "tool-link-userrights-readonly": "တူၺ်း {{GENDER:$1|ၽူႈၸႂ်ႉတိုဝ်း}} ၸုမ်း",
        "tool-link-emailuser": "သူင်ႇဢီးမေးလ်ဢၼ်ၼႆႉ {{GENDER:$1|ၽူႈၸႂ်ႉတိုဝ်း}}",
        "userpage": "တူၺ်းၼႃႈလိၵ်ႈၽူႈၸႂ်ႉတိုဝ်း",
        "projectpage": "တူၺ်းၼႃႈလိၵ်ႈ ပရေႃးၵျႅၵ်ႉ",
        "action-upload_by_url": "လုၵ်ႉၵႃႈတီႈ URL သေ လူတ်ႇၶိုၼ်ႈ ၾၢႆႇဢၼ်ၼႆႉ",
        "action-writeapi": "ၸႂ်ႉတိုဝ်း လွင်ႈတႅမ်ႈ API",
        "action-delete": "မွတ်ႇပႅတ်ႈၼႃႈလိၵ်ႈၼႆႉ",
-       "action-deleterevision": "မွတ်ႇပႅတ်ႈ ၶေႃႈၶူၼ်ႉလူဢၼ်ၼႆႉ",
-       "action-deletedhistory": "တူၺ်း ပိုၼ်းၸိူဝ်းမွတ်ႇပႅတ်ႈ ၶွင်ၼႃႈလိၵ်ႈၼႆႉ",
+       "action-deleterevision": "မွတ်ႇပႅတ်ႈ ၶေႃႈၶူၼ်ႉလူ",
+       "action-deletedhistory": "တူၺ်း ပိုၼ်းၸိူဝ်းမွတ်ႇပႅတ်ႈဝႆႉ ၶွင်ၼႃႈလိၵ်ႈၼႆႉ",
        "action-browsearchive": "ၶူၼ်ႉႁႃ ၼႃႈလိၵ်ႈ ၸိူဝ်းမွတ်ႇပႅတ်ႈၵႂႃႇ",
-       "action-undelete": "ဢဝ်ၶိုၼ်း ၼႃႈလိၵ်ႈၼႆ့",
+       "action-undelete": "ဢဝ်ၶိုၼ်း ၼႃႈလိၵ်ႈ",
        "action-suppressrevision": "တူၺ်းထတ်းၶိုၼ်း လႄႈ ၶိုၼ်းဢဝ် ၶေႃႈၶူၼ်ႉႁႃဢၼ်လပ်ႉသိူင်ႇဝႆႉ",
        "action-suppressionlog": "တူၺ်း သဵၼ်ႈမၢႆ သုၼ်ႇတူဝ်",
        "action-block": "​ႁႄႉတတ်း ၽူႈၸႂ်ႉတိုဝ်းတၢင်ႇၸိူဝ်း ၵႃႈတီႈ ၵၢၼ်မႄးထတ်း",
        "allpagessubmit": "ၶူၼ်ႉႁႃ",
        "allpagesprefix": "ၼႃးလိၵ်ႈဢၼ်ၼႄ ဢိၵ်ႇတင်း ၶေႃႈလူင်ႈၼႃႈ",
        "categories": "လိူင်ႈ",
+       "sp-deletedcontributions-contribs": "လွင်ႈၶဝ်ႈႁူမ်ႈ",
+       "linksearch": "သွၵ်ႈႁႃ ႁဵင်းၵွင်ႉၽၢႆႇၼွၵ်ႈ",
+       "linksearch-pat": "လၢႆးသွၵ်ႈႁႃႈ :",
+       "linksearch-ns": "ဢွင်ႈတီႈၸိုဝ်ႈ",
+       "linksearch-ok": "ၶူၼ်ႉႁႃ",
+       "linksearch-line": "$1 ၼႆႉ ၵွင်ႉဝႆႉၵႃႈတီႈ $2",
+       "listusersfrom": "ၼႄပၼ် ၽူႈၸႂ်ႉတိုဝ်း ၸိူဝ်းတႄႇမႃးတီႈ :",
+       "listusers-submit": "ၼႄ",
+       "listusers-noresult": "ဢမ်ႇႁၼ် ၽူႈၸႂ်ႉတိုဝ်း",
+       "listusers-blocked": "(ႁႄႉတတ်းဝႆႉ)",
+       "activeusers": "သဵၼ်ႈမၢႆ ၽူႈၸႂ်ႉတိုဝ်း ၸိူဝ်းတူင်ႉၼိုင်",
+       "activeusers-intro": "ၼႆႉတႄႉပဵၼ် သဵၼ်ႈမၢႆၽူႈၸႂ်ႉတိုဝ်း ၵေႃႉၸိူဝ်းဢၼ်မီး လွင်ႈတူင်ႉၼိုင် ၼႂ်းၵႄႈ ၵမ်းလိုၼ်းသုတ်း $1 {{PLURAL:$1|ဝၼ်း|ဝၼ်းလိူဝ်}}.",
+       "activeusers-count": "$1 {{PLURAL:$1|တူင်ႉၼိုင်|တူင်ႉၼိုင််}} in the last {{PLURAL:$3|ဝၼ်း|$3 ဝၼ်း}}",
+       "activeusers-from": "ၼႄပၼ် ၽူႈၸႂ်ႉတိုဝ်း ၸိူဝ်းတႄႇမႃးတီႈ :",
+       "activeusers-groups": "ၼႄပၼ် ၽူႈၸႂ်ႉတိုဝ်း ၸိူဝ်းပိူင်ႇၸူးၸုမ်း:",
+       "activeusers-excludegroups": "ပၢၵ်ႈဝႆႉ ၽူႈၸႂ်ႉတိုဝ်း ၸိူဝ်းပိူင်ႇၸူးၸုမ်း:",
+       "activeusers-noresult": "ဢမ်ႇႁၼ် ၽူႈၸႂ်ႉတိုဝ်း",
+       "activeusers-submit": "ၼႄပၼ် ၽူႈၸႂ်ႉတိုဝ်း ၸိူဝ်းမီးလွင်ႈတူင်ႉၼိုင်",
+       "listgrouprights": "သုၼ်ႇၸုမ်းၽူႈၸႂ်ႉတိုဝ်း",
+       "listgrouprights-summary": "ၸိူဝ်းၽၢႆႇတႂ်ႈၼႆႉ ပဵၼ်သဵၼ်ႈမၢႆၸုမ်းၽူႈၸႂ်ႉတိုဝ်း ၸိူဝ်းဢၼ်မၵ်းမၢႆဝႆႉတီႈ ဝီႇၶီႇဢၼ်ၼႆႉ၊ မၵ်းမၼ်ႈဝႆႉပၼ်ၸွမ်း သုၼ်ႇႁဵတ်းတွၼ်ႈတႃႇၶဝ်။ ၸိူဝ်းၼၼ်ႉတႄႉ တေၸၢင်ႈပဵၼ်လႆႈ [[{{MediaWiki:Listgrouprights-helppage}}|လွၼ်ႉၶၢဝ်ႇ ၸိူဝ်းထႅမ်သႂ်ႇဝႆႉ​ထႅင်ႈ]]",
+       "listgrouprights-key": "ၶေႃႈၶႆႈလၢတ်ႈ:\n* <span class=\"listgrouprights-granted\">သုၼ်ႇ ဢၼ်ၶႂၢင်းပၼ်ဝႆႉ</span>\n* <span class=\"listgrouprights-revoked\">သုၼ် ဢၼ်ယႃႉပႅတ်ႈဝႆႉ</span>",
+       "listgrouprights-group": "ၸုမ်း :",
+       "listgrouprights-rights": "သုၼ်ႇ",
+       "listgrouprights-helppage": "တၢင်းၸွႆႈထႅမ်ႈ:သုၼ်ႇၸုမ်း",
+       "listgrouprights-members": "(သဵၼ်ႈမၢႆ ၽူႈၸႂ်ႉတိုဝ်း)",
+       "listgrouprights-addgroup": "ထႅမ်သႂ်ႇ {{PLURAL:$2|ၸုမ်း|ၸုမ်း.}}: $1",
+       "listgrouprights-removegroup": "ထွၼ်ပႅတ်ႈ {{PLURAL:$2|ၸုမ်း|ၸုမ်း.}}: $1",
+       "listgrouprights-addgroup-all": "ထႅမ်သႂ်ႇ ၸုမ်းတင်းသဵင်ႈ",
+       "listgrouprights-removegroup-all": "ထွၼ်ပႅတ်ႈ ၸုမ်းတင်းသဵင်ႈ",
+       "listgrouprights-addgroup-self": "ထႅမ် {{PLURAL:$2|ၸုမ်း|ၸုမ်း.}} သႂ်ႇၸူး ဢၶွင်ႉၶွင်ၸဝ်ႈၵဝ်ႇ: $1",
+       "listgrouprights-removegroup-self": "ထွၼ် {{PLURAL:$2|ၸုမ်း|ၸုမ်း}}ပႅတ်ႈ ၵႃႈတီႈ ဢၶွင်ႉၶွင်ၸူဝ်ႈၵဝ်ႇ: $1",
+       "listgrouprights-addgroup-self-all": "ထႅမ်ၸုမ်းတင်းသဵင်ႈ သႂ်ႇတီႈ ဢၶွင်ႉၶွင်ၸဝ်ႈၵဝ်ႇ",
+       "listgrouprights-removegroup-self-all": "ထွၼ်ၸုမ်းတင်းသဵင်ႈပႅတ်ႈ ၵႃႈတီႈ ဢၶွင်ႉၶွင်ၸဝ်ႈၵဝ်ႇ",
+       "listgrouprights-namespaceprotection-header": "လွင်ႈမၵ်းၶၢၼ်း ဢွင်ႈၸိုဝ်ႈ",
+       "listgrouprights-namespaceprotection-namespace": "ဢွင်ႈတီႈၸိုဝ်ႈ",
+       "listgrouprights-namespaceprotection-restrictedto": "သုၼ်ႇ ၸိူဝ်းပၼ်သုၼ်ႇ ၽူႈၸႂ်ႉတိုဝ်း တွၼ်ႈတႃႇမႄးထတ်း",
+       "listgrants": "ပၼ်သုၼ်ႇ",
+       "listgrants-summary": "ၸိူဝ်းၽၢႆႇတႂ်ႈၼႆႉ ပဵၼ်သဵၼ်ႈမၢႆ ၸိူဝ်းပၼ်သုၼ်ႇ ဢၼ်ဢိၵ်ႇတင်း သုၼ်ႇႁဵတ်းလႆႈ တႃႇ ၽူႈၸႂ်ႉတိုဝ်း။ ၽူႈၸႂ်ႉတိုဝ်းၶဝ် ၸၢင်ႈၼႄႉၼွၼ်းပၼ်ဢႅပ်ႉ တွၼ်ႈတႃႇၸႂ်ႉတိုဝ်း ဢၶွင်ႉၶွင်ၶဝ်၊ ၵူၺ်းၵႃႈဝႃႈ ဢိင်ၼိူဝ် ၽူႈၸႂ်ႉတိုဝ်း ပၼ်သုၼ်ႇဢႅပ်ႉၵႃႈႁိုဝ်ၵေႃႈ တေမီး လွင်ႈမၵ်းၶၢၼ်းမွၵ်ႈၼၼ်ႉ။ ဢႅပ်ႉၼိုင်ႈဢၼ်ၼႆႉ မၼ်းတေၸၢင်ႈ ႁဵတ်းသၢင်ႇၼင်ႇ တူဝ်ၽူႈၸႂ်ႉတိုဝ်းသေတႃႉ မၼ်းတေဢမ်ႇမီးသုၼ်ႇၸႂ်ႉၼင်ႇ တူဝ်ၽူႈၸႂ်ႉတိုဝ်း ၸၢင်ႈၸႂ်ႉၼၼ်ႉ။\nၼၼ်ႉတႄႉ တေၸၢင်ႈပဵၼ်လႆႈ  [[{{MediaWiki:Listgrouprights-helppage}}|လွၼ်ႉၶၢဝ်ႇ ဢၼ်ထႅမ်သႂ်ႇဝႆႉထႅင်ႈ]] ဢၼ်ၵပ်းၵၢႆႇလူၺ်ႈ သုၼ်ႇဢၼ်လႂ်ဢၼ်ၼၼ်ႉ။",
+       "listgrants-grant": "ပၼ်သုၼ်ႇ",
+       "listgrants-rights": "သုၼ်ႇ",
+       "trackingcategories": "တွၼ်ႈၵၼ် ၸွမ်းႁွႆး",
+       "trackingcategories-msg": "တွၼ်ႈၵၼ် ၸွမ်းႁွႆး",
+       "trackingcategories-name": "ၸိုဝ်ႈ လိၵ်ႈပွင်ႇၶၢဝ်ႇ",
+       "restricted-displaytitle-ignored": "ၼႃႈလိၵ်ႈ ၸိူဝ်းၶၢမ်ႈဝႆႉ တႃႇၼႄႁူဝ်ၶေႃႈ",
+       "restricted-displaytitle-ignored-desc": "ၼႃႈလိၵ်ႈ ဢၼ်ၶၢမ်ႈဝႆႉ  <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> ယွၼ်ႉပိူဝ်ႈဝႃႈ မၼ်းဢမ်ႇမီး လွင်ႈငမ်ႇမႅၼ်ႈၵၼ်တင်း ႁူဝ်ၶေႃႈတႄႉတႄႉ ၶွင်ၼႃႈလိၵ်ႈ။",
+       "trackingcategories-nodesc": "​ၶေႃႈသပ်းလႅင်း ဢမ်ႇၶဝ်ႈပႃးဝႆႉ။",
+       "trackingcategories-disabled": "တွၼ်ႈၵၼ်ၼႆ့ ဢိုတ်းၸႂ်ႉဝႆႉ",
+       "mailnologin": "ဢမ်ႇသူင်ႇ ႁဵင်းလိၵ်ႈ",
+       "mailnologintext": "ၸဝ်ႈၵဝ်ႇ တေလႆႈ [[Special:UserLogin|လွၵ်ႉဢိၼ်ႇ]]ဝႆႉ သေ တီႈၼႂ်း [[Special:Preferences|preferences]]ၼၼ်ႉ တေလႆႈမီး ႁဵင်းလိၵ်ႈ ဢီးမေးလ် ၸဝ်ႈၵဝ်ႇ ဢၼ်ၸႂ်ႉလႆႈ တွၼ်ႈတႃႇသူင်ႇ ဢီးမေးလ်ၸူး ၽူႈၸႂ်ႉတိုဝ်းတၢင်ႇၵေႃႉ။",
+       "emailuser": "သူင်ႇဢီးမေးလ်ၸူး ၽူႈၸႂ်ႉတိုဝ်းၵေႃႉၼႆႉ",
+       "emailuser-title-target": "သူင်ႇဢီးမေးလ်ဢၼ်ၼႆႉ {{GENDER:$1|ၽူႈၸႂ်ႉတိုဝ်း}}",
+       "emailuser-title-notarget": "ၽူႈၸႂ်ႉတိုဝ်း ဢီးမေးလ်",
+       "defemailsubject": "{{SITENAME}} ဢီးမေးလ်ၵႃႈတီႈ ၽူႈၸႂ်ႉတိုဝ်း \"$1\"",
+       "usermaildisabled": "ဢီးမေးလ် ၽူႈၸႂ်ႉတိုဝ်း ဢိုတ်းၸႂ်ႉဝႆႉ",
+       "usermaildisabledtext": "ၸဝ်ႈၵဝ်ႇ တေဢမ်ႇၸၢင်ႈသူင်ႇ ဢီးမေးလ်ၸူး ၽူႈၸႂ်ႉတိုဝ်းတၢင်ႇၵေႃႉ ၸိူဝ်းမီးဝႆႉ တီႈၼိူဝ် ဝီႇၶီႇၼႆႉ",
+       "noemailtitle": "ဢမ်ႇမီး ႁဵင်းလိၵ်ႈဢီးမေးလ်",
+       "noemailtext": "ၽူႈၸႂ်ႉတိုဝ်းၵေႃႉၼႆႉ ဢမ်ႇလႆႈမၵ်းမၼ်ႈဝႆႉ ႁဵင်းလိၵ်ႈဢီးမေးလ် ဢၼ်ၸႂ်ႉလႆႈ။",
+       "nowikiemailtext": "ၽူႈၸႂ်ႉတိုဝ်းၵေႃႉၼႆႉ ထုၵ်ႇလိူၵ်ႈဝႆႉ တွၼ်ႈတႃႇ ဢမ်ႇႁၢပ်ႉဢဝ် ဢီးမေးလ် ၵႃႈတီႈ ၽူႈၸႂ်ႉတိုဝ်း တၢင်ႇၸိူဝ်း။",
+       "emailnotarget": "ဢမ်ႇမီးဝႆႉ ဢမ်ႇၼၼ် ၸိုဝ်ႈၽူႈၸႂ်ႉတိုဝ်း ၽိတ်းပိူင်ႈဝႆႉတွၼ်ႈတႃႇ ၽူႈႁပ်ႉဢဝ်။",
+       "emailtarget": "ထႅမ်သႂ်ႇပၼ် ၸိုဝ်ႈၽူႈၸႂ်ႉတိုဝ်း တႃႇ ၽူႈႁပ်ႉဢဝ်",
+       "emailusername": "ၸိုဝ်ႈၽူႈၸႂ်ႉတိုဝ်း :",
+       "emailusernamesubmit": "တၢင်ႇၼႄ",
+       "email-legend": "သူင်ႇဢီးမေးလ်ၸူး ၽူႈၸႂ်ႉတိုဝ်း  {{SITENAME}} တၢင်ႇၸိူဝ်း",
+       "emailfrom": "မႃးတီႈ :",
+       "emailto": "ထိုင်:",
+       "emailsubject": "လွင်ႈတၢင်း :",
+       "emailmessage": "ၶေႃႈၽၢၵ်ႇ",
+       "emailsend": "သူင်ႇ",
+       "emailccme": "ဢဝ်ၶေႃႈၽၢၵ်ႇၵဝ်ၶႃႈ ၶိုၼ်းထုတ်ႇသူင်ႇဢီးမေးလ်ပၼ် ၵဝ်ၶႃႈ",
+       "emailccsubject": "ထုတ်ႇဢဝ် ၶေႃႈၽၢၵ်ႇၸဝ်ႈၵဝ်ႇၸူး $1: $2",
+       "emailsent": "ဢီးမေးလ် သူင်ႇယဝ်ႉယဝ်ႈ",
+       "emailsenttext": "ဢီးမေးလ်ၸဝ်ႈၵဝ်ႇ ထုၵ်ႇသူင်ႇပၼ်ၵႂႃႇယဝ်ႉ။",
+       "emailuserfooter": "ဢီးမေးလ်ၼႆႉ  \"{{int:emailuser}}\" ၵၢၼ်တီႈ  {{SITENAME}} ၵေႃႉ {{GENDER:$1|သူင်ႇ}} $1 ၸူး  {{GENDER:$2|$2}။ သင်ၸိူဝ်ႉဝႃႈ  {{GENDER:$2|ၸဝ်ႈၵဝ်ႇ}} ၶႂ်ႈတွပ်ႇပၼ်ၶိုၼ်း ဢီးမေးလ်ဢၼ်ၼႆႉၼႆ ဢီးမေးလ် {{GENDER:$2|ၸဝ်ႈၵဝ်ႇ}} တေထုၵ်ႇ သူင်ႇပၼ်ၵႂႃႇၸူး {{GENDER:$1|ၽူႈသူင်ႇ ၵေႃႉငဝ်ႈတိုၼ်း}}၊ ႁဵင်းလိၵ်ႈဢီးမေးလ် {{GENDER:$2|ၸဝ်ႈၵဝ်ႇ}} တေၵႂႃႇ ဢွၵ်ႇၼႄတီႈ  {{GENDER:$1|ၶဝ်}}ယဝ်ႈ။",
        "mywatchlist": "သဵၼ်ႈမၢႆပႂ်ႉတူၺ်း",
        "watch": "ပႂ်ႉတူၺ်း",
        "dellogpage": "မွတ်ႇပႅတ်ႈ သၢႆမၢႆ",
index 3342fdd..0eb20ac 100644 (file)
        "password-change-forbidden": "Не можете да промените лозинку на овом викију.",
        "externaldberror": "Дошло је до грешке при препознавању базе података или немате овлашћења да ажурирате свој спољни налог.",
        "login": "Пријави ме",
-       "login-security": "Ð\92аеÑ\80иÑ\84икаÑ\86иÑ\98а Ð²Ð°Ñ\88ег Ð¸Ð½Ð´ÐµÐ½Ñ\82иÑ\82еÑ\82а",
+       "login-security": "Верификација вашег индентитета",
        "nav-login-createaccount": "Пријава/регистрација",
        "userlogin": "Пријава/регистрација",
        "userloginnocreate": "Пријава",
        "hist": "ист",
        "hide": "Сакриј",
        "show": "Прикажи",
-       "minoreditletter": " м",
+       "minoreditletter": "м",
        "newpageletter": "Н",
        "boteditletter": "б",
        "unpatrolledletter": "!",
index 1fc6573..16f3421 100644 (file)
        "hist": "ist",
        "hide": "Sakrij",
        "show": "Prikaži",
-       "minoreditletter": " m",
+       "minoreditletter": "m",
        "newpageletter": "N",
        "boteditletter": "b",
        "unpatrolledletter": "!",
index ac43471..c635007 100644 (file)
        "search-interwiki-caption": "సోదర ప్రాజెక్టులు",
        "search-interwiki-default": "$1 నుండి ఫలితాలు:",
        "search-interwiki-more": "(మరిన్ని)",
+       "search-interwiki-more-results": "మరిన్ని ఫలితాలు",
        "search-relatedarticle": "సంబంధించినవి",
        "searchrelated": "సంబంధించినవి",
        "searchall": "అన్నీ",
        "userrights-user-editname": "వాడుకరిపేరును ఇవ్వండి:",
        "editusergroup": "వాడుకరి గుంపులను చూపించు",
        "editinguser": "{{GENDER:$1|user}} వాడుకరి హక్కులను మారుస్తున్నారు <strong>[[User:$1|$1]]</strong> $2",
-       "userrights-editusergroup": "వాడుకరి సమూహాలను మార్చండి",
+       "userrights-editusergroup": "{{GENDER:$1|వాడుకరి}} సమూహాలను మార్చండి",
        "saveusergroups": "{{GENDER:$1|వాడుకరి}} గుంపులను భద్రపరచు",
        "userrights-groupsmember": "సభ్యులు:",
        "userrights-groupsmember-auto": "సంభావిత సభ్యులు:",
index d0060fd..f907c59 100644 (file)
@@ -46,6 +46,7 @@
        "tog-watchmoves": "Мин күчергән битләр һәм файллар күзәтү исемлегемә өстәлсен",
        "tog-watchdeletion": "Мин бетергән битләр һәм файлларны күзәтү исемлегемгә өстәлсен",
        "tog-watchuploads": "Минем тарафтан йөкләнелгән файлларны күзәтү исемлегемә кертергә",
+       "tog-watchrollback": "Мин үткәрмәгән битләрне күзәтү исемлегемә өстәргә",
        "tog-minordefault": "Барлык үзгәртүләрне килешү буенча кече дип билгеләнсен",
        "tog-previewontop": "Үзгәртү тәрәзәсеннән өстәрәк битне алдан карау өлкәсен күрсәтелсен",
        "tog-previewonfirst": "Үзгәртү битенә күчкәндә башта алдан карау бите күрсәтелсен",
        "talk": "Бәхәс",
        "views": "Караулар",
        "toolbox": "Кораллар",
+       "tool-link-userrights": "{{GENDER:$1|Кулланучының}} төркемнәрен алмаштыру",
+       "tool-link-userrights-readonly": "{{GENDER:$1|Кулланучының}} төркемнәрен карау",
+       "tool-link-emailuser": "{{GENDER:$1|Кулланучыга}} хат язу",
        "userpage": "Кулланучы битен карау",
        "projectpage": "Проект битен карау",
        "imagepage": "Файл битен карау",
        "suspicious-userlogout": "Сезнең эшчәнлекне бетерү соравыгыз кире кагылды, чөнки ул ялгыш браузер яисә кэшлаучы прокси аша җибәрелергэ мөмкин.",
        "pt-login": "Керү",
        "pt-login-button": "Керү",
+       "pt-login-continue-button": "Керүне дәвам итү",
        "pt-createaccount": "Яңа кулланучыны теркәү",
        "pt-userlogout": "Чыгу",
        "php-mail-error-unknown": "PHP mail() функциясендә билгесез хата",
index 27ef735..9044e45 100644 (file)
@@ -79,7 +79,8 @@
                        "1233thehongkonger",
                        "Maskers",
                        "Knch903",
-                       "Winstonyin"
+                       "Winstonyin",
+                       "Wmr"
                ]
        },
        "tog-underline": "底線標示連結:",
        "logentry-suppress-event-legacy": "$1 {{GENDER:$2|已暗中變更}} $3 中日誌的可見性",
        "logentry-suppress-revision-legacy": "$1 {{GENDER:$2|已暗中更改}}頁面 $3 中修訂的可見性",
        "revdelete-content-hid": "已隱藏內容",
-       "revdelete-summary-hid": "å·±隱藏摘要",
+       "revdelete-summary-hid": "å·²隱藏摘要",
        "revdelete-uname-hid": "隱藏使用者名稱",
        "revdelete-content-unhid": "取消隱藏內容",
        "revdelete-summary-unhid": "取消隱藏編輯摘要",
index befe957..7919dc4 100644 (file)
@@ -185,8 +185,26 @@ td.mw-submit {
 }
 
 input#wpSummary {
+       background-color: #fff;
+       color: #000;
        width: 80%;
        margin-bottom: 1em;
+       padding: 0.625em 0.546875em 0.546875em;
+       border: 1px solid #a2a9b1;
+       border-radius: 2px;
+       box-shadow: inset 0 0 0 1px #fff;
+       font-family: inherit;
+       font-size: inherit;
+       -webkit-transition: border-color 200ms cubic-bezier( 0.39, 0.575, 0.565, 1 ), box-shadow 200ms cubic-bezier( 0.39, 0.575, 0.565, 1 );
+       -moz-transition: border-color 200ms cubic-bezier( 0.39, 0.575, 0.565, 1 ), box-shadow 200ms cubic-bezier( 0.39, 0.575, 0.565, 1 );
+       transition: border-color 200ms cubic-bezier( 0.39, 0.575, 0.565, 1 ), box-shadow 200ms cubic-bezier( 0.39, 0.575, 0.565, 1 );
+}
+
+input#wpSummary:focus,
+input#wpSummary:active {
+       outline: 0;
+       border-color: #36c;
+       box-shadow: inset 0 0 0 1px #36c;
 }
 
 .mw-input-with-label {
index da9e59e..14a610b 100644 (file)
@@ -6,27 +6,31 @@
         * @mixins OO.EmitterList
         *
         * @constructor
+        * @param {string} name Group name
         * @param {Object} [config] Configuration options
-        * @cfg {string} [name] Group name
         * @cfg {string} [type='send_unselected_if_any'] Group type
         * @cfg {string} [title] Group title
         * @cfg {string} [separator='|'] Value separator for 'string_options' groups
-        * @cfg {string} [exclusionType='default'] Group exclusion type
         * @cfg {boolean} [active] Group is active
+        * @cfg {boolean} [fullCoverage] This filters in this group collectively cover all results
         */
-       mw.rcfilters.dm.FilterGroup = function MwRcfiltersDmFilterGroup( config ) {
+       mw.rcfilters.dm.FilterGroup = function MwRcfiltersDmFilterGroup( name, config ) {
                config = config || {};
 
                // Mixin constructor
                OO.EventEmitter.call( this );
                OO.EmitterList.call( this );
 
-               this.name = config.name;
+               this.name = name;
                this.type = config.type || 'send_unselected_if_any';
                this.title = config.title;
                this.separator = config.separator || '|';
-               this.exclusionType = config.exclusionType || 'default';
+
                this.active = !!config.active;
+               this.fullCoverage = !!config.fullCoverage;
+
+               this.aggregate( { update: 'filterItemUpdate' } );
+               this.connect( this, { filterItemUpdate: 'onFilterItemUpdate' } );
        };
 
        /* Initialization */
        /* Methods */
 
        /**
-        * Check the active status of the group and set it accordingly.
+        * Respond to filterItem update event
         *
         * @fires update
         */
-       mw.rcfilters.dm.FilterGroup.prototype.checkActive = function () {
-               var active,
-                       count = 0;
-
-               // Recheck group activity
-               this.getItems().forEach( function ( filterItem ) {
-                       count += Number( filterItem.isSelected() );
-               } );
-
-               active = (
-                       count > 0 &&
-                       count < this.getItemCount()
-               );
+       mw.rcfilters.dm.FilterGroup.prototype.onFilterItemUpdate = function () {
+               // Update state
+               var active = this.areAnySelected();
 
                if ( this.active !== active ) {
                        this.active = active;
                return this.name;
        };
 
+       /**
+        * Check whether there are any items selected
+        *
+        * @return {boolean} Any items in the group are selected
+        */
+       mw.rcfilters.dm.FilterGroup.prototype.areAnySelected = function () {
+               return this.getItems().some( function ( filterItem ) {
+                       return filterItem.isSelected();
+               } );
+       };
+
+       /**
+        * Check whether all items selected
+        *
+        * @return {boolean} All items are selected
+        */
+       mw.rcfilters.dm.FilterGroup.prototype.areAllSelected = function () {
+               return this.getItems().every( function ( filterItem ) {
+                       return filterItem.isSelected();
+               } );
+       };
+
+       /**
+        * Get all selected items in this group
+        *
+        * @param {mw.rcfilters.dm.FilterItem} [excludeItem] Item to exclude from the list
+        * @return {mw.rcfilters.dm.FilterItem[]} Selected items
+        */
+       mw.rcfilters.dm.FilterGroup.prototype.getSelectedItems = function ( excludeItem ) {
+               var excludeName = ( excludeItem && excludeItem.getName() ) || '';
+
+               return this.getItems().filter( function ( item ) {
+                       return item.getName() !== excludeName && item.isSelected();
+               } );
+       };
+
+       /**
+        * Check whether all selected items are in conflict with the given item
+        *
+        * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item to test
+        * @return {boolean} All selected items are in conflict with this item
+        */
+       mw.rcfilters.dm.FilterGroup.prototype.areAllSelectedInConflictWith = function ( filterItem ) {
+               var selectedItems = this.getSelectedItems( filterItem );
+
+               return selectedItems.length > 0 && selectedItems.every( function ( selectedFilter ) {
+                       return selectedFilter.existsInConflicts( filterItem );
+               } );
+       };
+
+       /**
+        * Check whether any of the selected items are in conflict with the given item
+        *
+        * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item to test
+        * @return {boolean} Any of the selected items are in conflict with this item
+        */
+       mw.rcfilters.dm.FilterGroup.prototype.areAnySelectedInConflictWith = function ( filterItem ) {
+               var selectedItems = this.getSelectedItems( filterItem );
+
+               return selectedItems.length > 0 && selectedItems.some( function ( selectedFilter ) {
+                       return selectedFilter.existsInConflicts( filterItem );
+               } );
+       };
+
        /**
         * Get group type
         *
        };
 
        /**
-        * Get group exclusion type
+        * Check whether the group is defined as full coverage
         *
-        * @return {string} Exclusion type
+        * @return {boolean} Group is full coverage
         */
-       mw.rcfilters.dm.FilterGroup.prototype.getExclusionType = function () {
-               return this.exclusionType;
+       mw.rcfilters.dm.FilterGroup.prototype.isFullCoverage = function () {
+               return this.fullCoverage;
        };
 }( mediaWiki ) );
index 5dfb68d..39c7667 100644 (file)
@@ -6,6 +6,7 @@
         *
         * @constructor
         * @param {string} name Filter name
+        * @param {mw.rcfilters.dm.FilterGroup} groupModel Filter group model
         * @param {Object} config Configuration object
         * @cfg {string} [group] The group this item belongs to
         * @cfg {string} [label] The label for the filter
         * @cfg {boolean} [active=true] The filter is active and affecting the result
         * @cfg {string[]} [excludes=[]] A list of filter names this filter, if
         *  selected, makes inactive.
-        * @cfg {boolean} [default] The default state of this filter
+        * @cfg {boolean} [selected] The item is selected
+        * @cfg {string[]} [subset] Defining the names of filters that are a subset of this filter
+        * @cfg {string[]} [conflictsWith] Defining the names of filters that conflict with this item
         */
-       mw.rcfilters.dm.FilterItem = function MwRcfiltersDmFilterItem( name, config ) {
+       mw.rcfilters.dm.FilterItem = function MwRcfiltersDmFilterItem( name, groupModel, config ) {
                config = config || {};
 
                // Mixin constructor
                OO.EventEmitter.call( this );
 
                this.name = name;
-               this.group = config.group || '';
+               this.groupModel = groupModel;
+
                this.label = config.label || this.name;
                this.description = config.description;
-               this.default = !!config.default;
+               this.selected = !!config.selected;
+
+               // Interaction definitions
+               this.subset = config.subset || [];
+               this.conflicts = config.conflicts || [];
+               this.superset = [];
 
-               this.active = config.active === undefined ? true : !!config.active;
-               this.excludes = config.excludes || [];
-               this.selected = this.default;
+               // Interaction states
+               this.included = false;
+               this.conflicted = false;
+               this.fullyCovered = false;
        };
 
        /* Initialization */
                return this.name;
        };
 
+       /**
+        * Get the model of the group this filter belongs to
+        *
+        * @return {mw.rcfilters.dm.FilterGroup} Filter group model
+        */
+       mw.rcfilters.dm.FilterItem.prototype.getGroupModel = function () {
+               return this.groupModel;
+       };
+
        /**
         * Get the group name this filter belongs to
         *
         * @return {string} Filter group name
         */
-       mw.rcfilters.dm.FilterItem.prototype.getGroup = function () {
-               return this.group;
+       mw.rcfilters.dm.FilterItem.prototype.getGroupName = function () {
+               return this.groupModel.getName();
        };
 
        /**
                return this.default;
        };
 
+       /**
+        * Get filter subset
+        * This is a list of filter names that are defined to be included
+        * when this filter is selected.
+        *
+        * @return {string[]} Filter subset
+        */
+       mw.rcfilters.dm.FilterItem.prototype.getSubset = function () {
+               return this.subset;
+       };
+
+       /**
+        * Get filter superset
+        * This is a generated list of filters that define this filter
+        * to be included when either of them is selected.
+        *
+        * @return {string[]} Filter superset
+        */
+       mw.rcfilters.dm.FilterItem.prototype.getSuperset = function () {
+               return this.superset;
+       };
+
        /**
         * Get the selected state of this filter
         *
        };
 
        /**
-        * Check if this filter is active
+        * Check whether the filter is currently in a conflict state
+        *
+        * @return {boolean} Filter is in conflict state
+        */
+       mw.rcfilters.dm.FilterItem.prototype.isConflicted = function () {
+               return this.conflicted;
+       };
+
+       /**
+        * Check whether the filter is currently in an already included subset
         *
-        * @return {boolean} Filter is active
+        * @return {boolean} Filter is in an already-included subset
         */
-       mw.rcfilters.dm.FilterItem.prototype.isActive = function () {
-               return this.active;
+       mw.rcfilters.dm.FilterItem.prototype.isIncluded = function () {
+               return this.included;
        };
 
        /**
-        * Check if this filter has a list of excluded filters
+        * Check whether the filter is currently fully covered
         *
-        * @return {boolean} Filter has a list of excluded filters
+        * @return {boolean} Filter is in fully-covered state
         */
-       mw.rcfilters.dm.FilterItem.prototype.hasExcludedFilters = function () {
-               return !!this.excludes.length;
+       mw.rcfilters.dm.FilterItem.prototype.isFullyCovered = function () {
+               return this.fullyCovered;
        };
 
        /**
-        * Get this filter's list of excluded filters
+        * Get filter conflicts
         *
-        * @return {string[]} Array of excluded filter names
+        * @return {string[]} Filter conflicts
         */
-       mw.rcfilters.dm.FilterItem.prototype.getExcludedFilters = function () {
-               return this.excludes;
+       mw.rcfilters.dm.FilterItem.prototype.getConflicts = function () {
+               return this.conflicts;
        };
 
        /**
-        * Toggle the active state of the item
+        * Set filter conflicts
         *
-        * @param {boolean} [isActive] Filter is active
+        * @param {string[]} conflicts Filter conflicts
+        */
+       mw.rcfilters.dm.FilterItem.prototype.setConflicts = function ( conflicts ) {
+               this.conflicts = conflicts || [];
+       };
+
+       /**
+        * Set filter superset
+        *
+        * @param {string[]} superset Filter superset
+        */
+       mw.rcfilters.dm.FilterItem.prototype.setSuperset = function ( superset ) {
+               this.superset = superset || [];
+       };
+
+       /**
+        * Check whether a filter exists in the subset list for this filter
+        *
+        * @param {string} filterName Filter name
+        * @return {boolean} Filter name is in the subset list
+        */
+       mw.rcfilters.dm.FilterItem.prototype.existsInSubset = function ( filterName ) {
+               return this.subset.indexOf( filterName ) > -1;
+       };
+
+       /**
+        * Check whether this item has a potential conflict with the given item
+        *
+        * This checks whether the given item is in the list of conflicts of
+        * the current item, but makes no judgment about whether the conflict
+        * is currently at play (either one of the items may not be selected)
+        *
+        * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item
+        * @return {boolean} This item has a conflict with the given item
+        */
+       mw.rcfilters.dm.FilterItem.prototype.existsInConflicts = function ( filterItem ) {
+               return this.conflicts.indexOf( filterItem.getName() ) > -1;
+       };
+
+       /**
+        * Set the state of this filter as being conflicted
+        * (This means any filters in its conflicts are selected)
+        *
+        * @param {boolean} [conflicted] Filter is in conflict state
+        * @fires update
+        */
+       mw.rcfilters.dm.FilterItem.prototype.toggleConflicted = function ( conflicted ) {
+               conflicted = conflicted === undefined ? !this.conflicted : conflicted;
+
+               if ( this.conflicted !== conflicted ) {
+                       this.conflicted = conflicted;
+                       this.emit( 'update' );
+               }
+       };
+
+       /**
+        * Set the state of this filter as being already included
+        * (This means any filters in its superset are selected)
+        *
+        * @param {boolean} [included] Filter is included as part of a subset
         * @fires update
         */
-       mw.rcfilters.dm.FilterItem.prototype.toggleActive = function ( isActive ) {
-               isActive = isActive === undefined ? !this.active : isActive;
+       mw.rcfilters.dm.FilterItem.prototype.toggleIncluded = function ( included ) {
+               included = included === undefined ? !this.included : included;
 
-               if ( this.active !== isActive ) {
-                       this.active = isActive;
+               if ( this.included !== included ) {
+                       this.included = included;
                        this.emit( 'update' );
                }
        };
                        this.emit( 'update' );
                }
        };
+
+       /**
+        * Toggle the fully covered state of the item
+        *
+        * @param {boolean} [isFullyCovered] Filter is fully covered
+        * @fires update
+        */
+       mw.rcfilters.dm.FilterItem.prototype.toggleFullyCovered = function ( isFullyCovered ) {
+               isFullyCovered = isFullyCovered === undefined ? !this.fullycovered : isFullyCovered;
+
+               if ( this.fullyCovered !== isFullyCovered ) {
+                       this.fullyCovered = isFullyCovered;
+                       this.emit( 'update' );
+               }
+       };
 }( mediaWiki ) );
index 5bbeabf..13f7d31 100644 (file)
                OO.EmitterList.call( this );
 
                this.groups = {};
-               this.excludedByMap = {};
                this.defaultParams = {};
                this.defaultFiltersEmpty = null;
 
                // Events
                this.aggregate( { update: 'filterItemUpdate' } );
-               this.connect( this, { filterItemUpdate: 'onFilterItemUpdate' } );
+               this.connect( this, { filterItemUpdate: [ 'emit', 'itemUpdate' ] } );
        };
 
        /* Initialization */
        /* Methods */
 
        /**
-        * Respond to filter item change.
+        * Re-assess the states of filter items based on the interactions between them
         *
-        * @param {mw.rcfilters.dm.FilterItem} item Updated filter
-        * @fires itemUpdate
+        * @param {mw.rcfilters.dm.FilterItem} [item] Changed item. If not given, the
+        *  method will go over the state of all items
         */
-       mw.rcfilters.dm.FiltersViewModel.prototype.onFilterItemUpdate = function ( item ) {
-               // Reapply the active state of filters
-               this.reapplyActiveFilters( item );
+       mw.rcfilters.dm.FiltersViewModel.prototype.reassessFilterInteractions = function ( item ) {
+               var allSelected,
+                       model = this,
+                       iterationItems = item !== undefined ? [ item ] : this.getItems();
 
-               // Recheck group activity state
-               this.getGroup( item.getGroup() ).checkActive();
+               iterationItems.forEach( function ( checkedItem ) {
+                       var allCheckedItems = checkedItem.getSubset().concat( [ checkedItem.getName() ] ),
+                               groupModel = checkedItem.getGroupModel();
 
-               this.emit( 'itemUpdate', item );
-       };
+                       // Check for subsets (included filters) plus the item itself:
+                       allCheckedItems.forEach( function ( filterItemName ) {
+                               var itemInSubset = model.getItemByName( filterItemName );
 
-       /**
-        * Calculate the active state of the filters, based on selected filters in the group.
-        *
-        * @param {mw.rcfilters.dm.FilterItem} item Changed item
-        */
-       mw.rcfilters.dm.FiltersViewModel.prototype.reapplyActiveFilters = function ( item ) {
-               var selectedItemsCount,
-                       group = item.getGroup(),
-                       model = this;
-               if (
-                       !this.getGroup( group ).getExclusionType() ||
-                       this.getGroup( group ).getExclusionType() === 'default'
-               ) {
-                       // Default behavior
-                       // If any parameter is selected, but:
-                       // - If there are unselected items in the group, they are inactive
-                       // - If the entire group is selected, all are inactive
-
-                       // Check what's selected in the group
-                       selectedItemsCount = this.getGroupFilters( group ).filter( function ( filterItem ) {
-                               return filterItem.isSelected();
-                       } ).length;
-
-                       this.getGroupFilters( group ).forEach( function ( filterItem ) {
-                               filterItem.toggleActive(
-                                       selectedItemsCount > 0 ?
-                                               // If some items are selected
-                                               (
-                                                       selectedItemsCount === model.groups[ group ].getItemCount() ?
-                                                       // If **all** items are selected, they're all inactive
-                                                       false :
-                                                       // If not all are selected, then the selected are active
-                                                       // and the unselected are inactive
-                                                       filterItem.isSelected()
-                                               ) :
-                                               // No item is selected, everything is active
-                                               true
+                               itemInSubset.toggleIncluded(
+                                       // If any of itemInSubset's supersets are selected, this item
+                                       // is included
+                                       itemInSubset.getSuperset().some( function ( supersetName ) {
+                                               return ( model.getItemByName( supersetName ).isSelected() );
+                                       } )
                                );
                        } );
-               } else if ( this.getGroup( group ).getExclusionType() === 'explicit' ) {
-                       // Explicit behavior
-                       // - Go over the list of excluded filters to change their
-                       //   active states accordingly
-
-                       // For each item in the list, see if there are other selected
-                       // filters that also exclude it. If it does, it will still be
-                       // inactive.
-
-                       item.getExcludedFilters().forEach( function ( filterName ) {
-                               var filterItem = model.getItemByName( filterName );
-
-                               // Note to reduce confusion:
-                               // - item is the filter whose state changed and should exclude the other filters
-                               //   in its list of exclusions
-                               // - filterItem is the filter that is potentially being excluded by the current item
-                               // - anotherExcludingFilter is any other filter that excludes filterItem; we must check
-                               //   if that filter is selected, because if it is, we should not touch the excluded item
-                               if (
-                                       // Check if there are any filters (other than the current one)
-                                       // that also exclude the filterName
-                                       !model.excludedByMap[ filterName ].some( function ( anotherExcludingFilterName ) {
-                                               var anotherExcludingFilter = model.getItemByName( anotherExcludingFilterName );
-
-                                               return (
-                                                       anotherExcludingFilterName !== item.getName() &&
-                                                       anotherExcludingFilter.isSelected()
-                                               );
-                                       } )
-                               ) {
-                                       // Only change the state for filters that aren't
-                                       // also affected by other excluding selected filters
-                                       filterItem.toggleActive( !item.isSelected() );
+
+                       // Update coverage for the changed group
+                       if ( groupModel.isFullCoverage() ) {
+                               allSelected = groupModel.areAllSelected();
+                               groupModel.getItems().forEach( function ( filterItem ) {
+                                       filterItem.toggleFullyCovered( allSelected );
+                               } );
+                       }
+               } );
+
+               // Check for conflicts
+               // In this case, we must go over all items, since
+               // conflicts are bidirectional and depend not only on
+               // individual items, but also on the selected states of
+               // the groups they're in.
+               this.getItems().forEach( function ( filterItem ) {
+                       var inConflict = false,
+                               filterItemGroup = filterItem.getGroupModel();
+
+                       // For each item, see if that item is still conflicting
+                       $.each( model.groups, function ( groupName, groupModel ) {
+                               if ( filterItem.getGroupName() === groupName ) {
+                                       // Check inside the group
+                                       inConflict = groupModel.areAnySelectedInConflictWith( filterItem );
+                               } else {
+                                       // According to the spec, if two items conflict from two different
+                                       // groups, the conflict only lasts if the groups **only have selected
+                                       // items that are conflicting**. If a group has selected items that
+                                       // are conflicting and non-conflicting, the scope of the result has
+                                       // expanded enough to completely remove the conflict.
+
+                                       // For example, see two groups with conflicts:
+                                       // userExpLevel: [
+                                       //   {
+                                       //      name: 'experienced',
+                                       //      conflicts: [ 'unregistered' ]
+                                       //   }
+                                       // ],
+                                       // registration: [
+                                       //   {
+                                       //      name: 'registered',
+                                       //   },
+                                       //   {
+                                       //      name: 'unregistered',
+                                       //   }
+                                       // ]
+                                       // If we select 'experienced', then 'unregistered' is in conflict (and vice versa),
+                                       // because, inherently, 'experienced' filter only includes registered users, and so
+                                       // both filters are in conflict with one another.
+                                       // However, the minute we select 'registered', the scope of our results
+                                       // has expanded to no longer have a conflict with 'experienced' filter, and
+                                       // so the conflict is removed.
+
+                                       // In our case, we need to check if the entire group conflicts with
+                                       // the entire item's group, so we follow the above spec
+                                       inConflict = (
+                                               // The foreign group is in conflict with this item
+                                               groupModel.areAllSelectedInConflictWith( filterItem ) &&
+                                               // Every selected member of the item's own group is also
+                                               // in conflict with the other group
+                                               filterItemGroup.getSelectedItems().every( function ( otherGroupItem ) {
+                                                       return groupModel.areAllSelectedInConflictWith( otherGroupItem );
+                                               } )
+                                       );
                                }
+
+                               // If we're in conflict, this will return 'false' which
+                               // will break the loop. Otherwise, we're not in conflict
+                               // and the loop continues
+                               return !inConflict;
                        } );
-               }
+
+                       // Toggle the item state
+                       filterItem.toggleConflicted( inConflict );
+               } );
        };
 
        /**
         * @param {Object} filters Filter group definition
         */
        mw.rcfilters.dm.FiltersViewModel.prototype.initializeFilters = function ( filters ) {
-               var i, filterItem, selectedFilterNames, excludedFilters,
+               var i, filterItem, selectedFilterNames,
                        model = this,
                        items = [],
-                       addToMap = function ( excludedFilters ) {
-                               excludedFilters.forEach( function ( filterName ) {
-                                       model.excludedByMap[ filterName ] = model.excludedByMap[ filterName ] || [];
-                                       model.excludedByMap[ filterName ].push( filterItem.getName() );
+                       addArrayElementsUnique = function ( arr, elements ) {
+                               elements = Array.isArray( elements ) ? elements : [ elements ];
+
+                               elements.forEach( function ( element ) {
+                                       if ( arr.indexOf( element ) === -1 ) {
+                                               arr.push( element );
+                                       }
                                } );
-                       };
+
+                               return arr;
+                       },
+                       conflictMap = {},
+                       supersetMap = {};
 
                // Reset
                this.clearItems();
                this.groups = {};
-               this.excludedByMap = {};
 
                $.each( filters, function ( group, data ) {
                        if ( !model.groups[ group ] ) {
-                               model.groups[ group ] = new mw.rcfilters.dm.FilterGroup( {
-                                       name: group,
+                               model.groups[ group ] = new mw.rcfilters.dm.FilterGroup( group, {
                                        type: data.type,
                                        title: data.title,
                                        separator: data.separator,
-                                       exclusionType: data.exclusionType
+                                       fullCoverage: !!data.fullCoverage
                                } );
                        }
 
                        selectedFilterNames = [];
                        for ( i = 0; i < data.filters.length; i++ ) {
-                               excludedFilters = data.filters[ i ].excludes || [];
-
-                               filterItem = new mw.rcfilters.dm.FilterItem( data.filters[ i ].name, {
+                               filterItem = new mw.rcfilters.dm.FilterItem( data.filters[ i ].name, model.groups[ group ], {
                                        group: group,
                                        label: data.filters[ i ].label,
                                        description: data.filters[ i ].description,
-                                       selected: data.filters[ i ].selected,
-                                       excludes: excludedFilters,
-                                       'default': data.filters[ i ].default
+                                       subset: data.filters[ i ].subset
                                } );
 
-                               // Map filters and what excludes them
-                               addToMap( excludedFilters );
+                               // For convenience, we should store each filter's "supersets" -- these are
+                               // the filters that have that item in their subset list. This will just
+                               // make it easier to go through whether the item has any other items
+                               // that affect it (and are selected) at any given time
+                               if ( data.filters[ i ].subset ) {
+                                       data.filters[ i ].subset.forEach( function ( subsetFilterName ) { // eslint-disable-line no-loop-func
+                                               supersetMap[ subsetFilterName ] = supersetMap[ subsetFilterName ] || [];
+                                               addArrayElementsUnique(
+                                                       supersetMap[ subsetFilterName ],
+                                                       filterItem.getName()
+                                               );
+                                       } );
+                               }
+
+                               // Conflicts are bi-directional, which means FilterA can define having
+                               // a conflict with FilterB, and this conflict should appear in **both**
+                               // filter definitions.
+                               // We need to remap all the 'conflicts' so they reflect the entire state
+                               // in either direction regardless of which filter defined the other as conflicting.
+                               if ( data.filters[ i ].conflicts ) {
+                                       conflictMap[ filterItem.getName() ] = conflictMap[ filterItem.getName() ] || [];
+                                       addArrayElementsUnique(
+                                               conflictMap[ filterItem.getName() ],
+                                               data.filters[ i ].conflicts
+                                       );
+
+                                       data.filters[ i ].conflicts.forEach( function ( conflictingFilterName ) { // eslint-disable-line no-loop-func
+                                               // Add this filter to the conflicts of each of the filters in its list
+                                               conflictMap[ conflictingFilterName ] = conflictMap[ conflictingFilterName ] || [];
+                                               addArrayElementsUnique(
+                                                       conflictMap[ conflictingFilterName ],
+                                                       filterItem.getName()
+                                               );
+                                       } );
+                               }
 
                                if ( data.type === 'send_unselected_if_any' ) {
                                        // Store the default parameter state
                        }
                } );
 
+               items.forEach( function ( filterItem ) {
+                       // Apply conflict map to the items
+                       // Now that we mapped all items and conflicts bi-directionally
+                       // we need to apply the definition to each filter again
+                       filterItem.setConflicts( conflictMap[ filterItem.getName() ] );
+
+                       // Apply the superset map
+                       filterItem.setSuperset( supersetMap[ filterItem.getName() ] );
+               } );
+
+               // Add items to the model
                this.addItems( items );
 
                this.emit( 'initialize' );
                return this.groups;
        };
 
-       /**
-        * Update the representation of the parameters. These are the back-end
-        * parameters representing the filters, but they represent the given
-        * current state regardless of validity.
-        *
-        * This should only run after filters are already set.
-        *
-        * @param {Object} params Parameter state
-        */
-       mw.rcfilters.dm.FiltersViewModel.prototype.updateParameters = function ( params ) {
-               var model = this;
-
-               $.each( params, function ( name, value ) {
-                       // Only store the parameters that exist in the system
-                       if ( model.getItemByName( name ) ) {
-                               model.parameters[ name ] = value;
-                       }
-               } );
-       };
-
        /**
         * Get the value of a specific parameter
         *
                for ( i = 0; i < items.length; i++ ) {
                        result[ items[ i ].getName() ] = {
                                selected: items[ i ].isSelected(),
-                               active: items[ i ].isActive()
+                               conflicted: items[ i ].isConflicted(),
+                               included: items[ i ].isIncluded()
                        };
                }
 
                        filterItem = model.getItemByName( paramName );
                        // Ignore if no filter item exists
                        if ( filterItem ) {
-                               groupMap[ filterItem.getGroup() ] = groupMap[ filterItem.getGroup() ] || {};
+                               groupMap[ filterItem.getGroupName() ] = groupMap[ filterItem.getGroupName() ] || {};
 
                                // Mark the group if it has any items that are selected
-                               groupMap[ filterItem.getGroup() ].hasSelected = (
-                                       groupMap[ filterItem.getGroup() ].hasSelected ||
+                               groupMap[ filterItem.getGroupName() ].hasSelected = (
+                                       groupMap[ filterItem.getGroupName() ].hasSelected ||
                                        !!Number( paramValue )
                                );
 
                                // Add the relevant filter into the group map
-                               groupMap[ filterItem.getGroup() ].filters = groupMap[ filterItem.getGroup() ].filters || [];
-                               groupMap[ filterItem.getGroup() ].filters.push( filterItem );
+                               groupMap[ filterItem.getGroupName() ].filters = groupMap[ filterItem.getGroupName() ].filters || [];
+                               groupMap[ filterItem.getGroupName() ].filters.push( filterItem );
                        } else if ( model.groups.hasOwnProperty( paramName ) ) {
                                // This parameter represents a group (values are the filters)
                                // this is equivalent to checking if the group is 'string_options'
                // item label starting with the query string
                for ( i = 0; i < items.length; i++ ) {
                        if ( items[ i ].getLabel().toLowerCase().indexOf( query ) === 0 ) {
-                               result[ items[ i ].getGroup() ] = result[ items[ i ].getGroup() ] || [];
-                               result[ items[ i ].getGroup() ].push( items[ i ] );
+                               result[ items[ i ].getGroupName() ] = result[ items[ i ].getGroupName() ] || [];
+                               result[ items[ i ].getGroupName() ].push( items[ i ] );
                        }
                }
 
                if ( $.isEmptyObject( result ) ) {
                        // item containing the query string in their label, description, or group title
                        for ( i = 0; i < items.length; i++ ) {
-                               groupTitle = this.getGroup( items[ i ].getGroup() ).getTitle();
+                               groupTitle = items[ i ].getGroupModel().getTitle();
                                if (
                                        items[ i ].getLabel().toLowerCase().indexOf( query ) > -1 ||
                                        items[ i ].getDescription().toLowerCase().indexOf( query ) > -1 ||
                                        groupTitle.toLowerCase().indexOf( query ) > -1
                                ) {
-                                       result[ items[ i ].getGroup() ] = result[ items[ i ].getGroup() ] || [];
-                                       result[ items[ i ].getGroup() ].push( items[ i ] );
+                                       result[ items[ i ].getGroupName() ] = result[ items[ i ].getGroupName() ] || [];
+                                       result[ items[ i ].getGroupName() ].push( items[ i ] );
                                }
                        }
                }
index 88f32b4..ff34bb8 100644 (file)
 
        /**
         * Initialize the filter and parameter states
+        *
+        * @param {Object} filterStructure Filter definition and structure for the model
         */
-       mw.rcfilters.Controller.prototype.initialize = function () {
-               this.updateFromURL();
-       };
-
-       /**
-        * Update the model state based on the URL parameters.
-        */
-       mw.rcfilters.Controller.prototype.updateFromURL = function () {
+       mw.rcfilters.Controller.prototype.initialize = function ( filterStructure ) {
                var uri = new mw.Uri();
 
+               // Initialize the model
+               this.filtersModel.initializeFilters( filterStructure );
+
+               // Set filter states based on defaults and URL params
                this.filtersModel.updateFilters(
-                       // Translate the url params to filter select states
-                       this.filtersModel.getFiltersFromParameters( uri.query )
+                       this.filtersModel.getFiltersFromParameters(
+                               // Merge defaults with URL params for initialization
+                               $.extend(
+                                       true,
+                                       {},
+                                       this.filtersModel.getDefaultParams(),
+                                       // URI query overrides defaults
+                                       uri.query
+                               )
+                       )
                );
+
+               // Check all filter interactions
+               this.filtersModel.reassessFilterInteractions();
        };
 
        /**
                var obj = {};
 
                obj[ filterName ] = isSelected;
+
                this.filtersModel.updateFilters( obj );
                this.updateURL();
                this.updateChangesList();
+
+               // Check filter interactions
+               this.filtersModel.reassessFilterInteractions( this.filtersModel.getItemByName( filterName ) );
        };
 
        /**
index ef0489c..61df2e8 100644 (file)
                        new mw.rcfilters.ui.FormWrapperWidget(
                                changesListModel, $( '.rcoptions form' ) );
 
-                       filtersModel.initializeFilters( {
+                       controller.initialize( {
                                registration: {
                                        title: mw.msg( 'rcfilters-filtergroup-registration' ),
                                        type: 'send_unselected_if_any',
+                                       fullCoverage: true,
                                        filters: [
                                                {
                                                        name: 'hideliu',
                                        // ** In this case, the parameter name is the group name. **
                                        type: 'string_options',
                                        separator: ',',
+                                       fullCoverage: false,
                                        filters: [
                                                {
                                                        name: 'newcomer',
                                                        label: mw.msg( 'rcfilters-filter-userExpLevel-newcomer-label' ),
-                                                       description: mw.msg( 'rcfilters-filter-userExpLevel-newcomer-description' )
+                                                       description: mw.msg( 'rcfilters-filter-userExpLevel-newcomer-description' ),
+                                                       conflicts: [ 'hideanons' ]
                                                },
                                                {
                                                        name: 'learner',
                                                        label: mw.msg( 'rcfilters-filter-userExpLevel-learner-label' ),
-                                                       description: mw.msg( 'rcfilters-filter-userExpLevel-learner-description' )
+                                                       description: mw.msg( 'rcfilters-filter-userExpLevel-learner-description' ),
+                                                       conflicts: [ 'hideanons' ]
                                                },
                                                {
                                                        name: 'experienced',
                                                        label: mw.msg( 'rcfilters-filter-userExpLevel-experienced-label' ),
-                                                       description: mw.msg( 'rcfilters-filter-userExpLevel-experienced-description' )
+                                                       description: mw.msg( 'rcfilters-filter-userExpLevel-experienced-description' ),
+                                                       conflicts: [ 'hideanons' ]
                                                }
                                        ]
                                },
@@ -80,6 +85,7 @@
                                        // the functionality to the UI, whether we are dealing with 2
                                        // parameters in the group or more.
                                        type: 'send_unselected_if_any',
+                                       fullCoverage: true,
                                        filters: [
                                                {
                                                        name: 'hidemyself',
                                automated: {
                                        title: mw.msg( 'rcfilters-filtergroup-automated' ),
                                        type: 'send_unselected_if_any',
+                                       fullCoverage: true,
                                        filters: [
                                                {
                                                        name: 'hidebots',
                                significance: {
                                        title: mw.msg( 'rcfilters-filtergroup-significance' ),
                                        type: 'send_unselected_if_any',
+                                       fullCoverage: true,
                                        filters: [
                                                {
                                                        name: 'hideminor',
                                changetype: {
                                        title: mw.msg( 'rcfilters-filtergroup-changetype' ),
                                        type: 'send_unselected_if_any',
+                                       fullCoverage: true,
                                        filters: [
                                                {
                                                        name: 'hidepageedits',
                        $( '.rcoptions' ).before( filtersWidget.$element );
                        $( 'body' ).append( $overlay );
 
-                       // Initialize values
-                       controller.initialize();
-
                        // HACK: Remove old-style filter links for filters handled by the widget
                        // Ideally the widget would handle all filters and we'd just remove .rcshowhide entirely
                        $( '.rcshowhide' ).children().each( function () {
index 4ea88b5..8a9ad54 100644 (file)
@@ -7,4 +7,8 @@
                // Fix the positioning of the popup itself
                margin-top: 1em;
        }
+
+       &-muted {
+               opacity: 0.5;
+       }
 }
index c409d58..6c11cdb 100644 (file)
                color: #54595d;
        }
 
-       &-item-inactive {
-               opacity: 0.5;
-       }
-
        &-emptyFilters {
                color: #72777d;
        }
index 9f9e6fc..a874416 100644 (file)
@@ -20,7 +20,7 @@
                margin: 0 !important;
        }
 
-       &-inactive {
+       &-muted {
                opacity: 0.5;
        }
 }
index ca47f16..40d31c5 100644 (file)
@@ -57,6 +57,8 @@
                        .addClass( 'mw-rcfilters-ui-capsuleItemWidget' )
                        .on( 'mouseover', this.onHover.bind( this, true ) )
                        .on( 'mouseout', this.onHover.bind( this, false ) );
+
+               this.setCurrentMuteState();
        };
 
        OO.inheritClass( mw.rcfilters.ui.CapsuleItemWidget, OO.ui.CapsuleItemWidget );
         * Respond to model update event
         */
        mw.rcfilters.ui.CapsuleItemWidget.prototype.onModelUpdate = function () {
-               // Deal with active/inactive capsule filter items
+               this.setCurrentMuteState();
+       };
+
+       /**
+        * Set the current mute state for this item
+        */
+       mw.rcfilters.ui.CapsuleItemWidget.prototype.setCurrentMuteState = function () {
                this.$element
                        .toggleClass(
-                               'mw-rcfilters-ui-filterCapsuleMultiselectWidget-item-inactive',
-                               !this.model.isActive()
+                               'mw-rcfilters-ui-capsuleItemWidget-muted',
+                               this.model.isIncluded() ||
+                               this.model.isConflicted() ||
+                               this.model.isFullyCovered()
                        );
        };
 
        mw.rcfilters.ui.CapsuleItemWidget.prototype.onCapsuleRemovedByUser = function () {
                this.controller.updateFilter( this.model.getName(), false );
        };
+
+       /**
+        * Remove and destroy external elements of this widget
+        */
+       mw.rcfilters.ui.CapsuleItemWidget.prototype.destroy = function () {
+               // Destroy the popup
+               this.popup.$element.detach();
+
+               // Disconnect events
+               this.model.disconnect( this );
+               this.closeButton.disconnect( this );
+       };
 }( mediaWiki, jQuery ) );
index 56303d5..9bb1a98 100644 (file)
                this.menu.selectItem();
                this.menu.highlightItem();
        };
+
+       /**
+        * @inheritdoc
+        */
+       mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.removeItems = function ( items ) {
+               // Parent call
+               mw.rcfilters.ui.FilterCapsuleMultiselectWidget.parent.prototype.removeItems.call( this, items );
+
+               // Destroy the item widget when it is removed
+               // This is done because we re-add items by recreating them, rather than hiding them
+               // and items include popups, that will just continue to be created and appended
+               // unnecessarily.
+               items.forEach( function ( widget ) {
+                       widget.destroy();
+               } );
+       };
+
 }( mediaWiki, jQuery ) );
index f9829d4..9bf26d1 100644 (file)
@@ -48,6 +48,7 @@
                // Event
                this.checkboxWidget.connect( this, { userChange: 'onCheckboxChange' } );
                this.model.connect( this, { update: 'onModelUpdate' } );
+               this.model.getGroupModel().connect( this, { update: 'onGroupModelUpdate' } );
 
                this.$element
                        .addClass( 'mw-rcfilters-ui-filterItemWidget' )
        mw.rcfilters.ui.FilterItemWidget.prototype.onModelUpdate = function () {
                this.checkboxWidget.setSelected( this.model.isSelected() );
 
+               this.setCurrentMuteState();
+       };
+
+       /**
+        * Respond to item group model update event
+        */
+       mw.rcfilters.ui.FilterItemWidget.prototype.onGroupModelUpdate = function () {
+               this.setCurrentMuteState();
+       };
+
+       /**
+        * Set the current mute state for this item
+        */
+       mw.rcfilters.ui.FilterItemWidget.prototype.setCurrentMuteState = function () {
                this.$element.toggleClass(
-                       'mw-rcfilters-ui-filterItemWidget-inactive',
-                       !this.model.isActive()
+                       'mw-rcfilters-ui-filterItemWidget-muted',
+                       this.model.isConflicted() ||
+                       this.model.isIncluded() ||
+                       this.model.isFullyCovered() ||
+                       (
+                               // Item is also muted when any of the items in its group is active
+                               this.model.getGroupModel().isActive() &&
+                               // But it isn't selected
+                               !this.model.isSelected()
+                       )
                );
        };
-
        /**
         * Get the name of this filter
         *
index ad58583..c989c83 100644 (file)
                                                                                booklet.setPage( '|results|' );
                                                                        } ).setDisabled( !paramsAreForced ) ).$element,
                                                                        new OO.ui.PopupButtonWidget( {
+                                                                               $overlay: $( '#mw-apisandbox-ui' ),
                                                                                framed: false,
                                                                                icon: 'info',
                                                                                popup: {
index fceeb64..4df2df7 100644 (file)
@@ -8,6 +8,7 @@
  * @singleton
  */
 
+/* global mwNow */
 /* eslint-disable no-use-before-define */
 
 ( function ( $ ) {
                 *
                 * @return {number} Current time
                 */
-               now: ( function () {
-                       var perf = window.performance,
-                               navStart = perf && perf.timing && perf.timing.navigationStart;
-                       return navStart && typeof perf.now === 'function' ?
-                               function () { return navStart + perf.now(); } :
-                               function () { return +new Date(); };
-               }() ),
+               now: mwNow,
+               // mwNow is defined in startup.js
 
                /**
                 * Format a string. Replace $1, $2 ... $N with positional arguments.
                        return $.when.apply( $, all );
                } );
                loading.then( function () {
+                       /* global mwPerformance */
                        mwPerformance.mark( 'mwLoadEnd' );
                        mw.hook( 'resourceloader.loadEnd' ).fire();
                } );
index 20f8efb..2e09392 100644 (file)
         *
         * @class mw.SafeStorage
         * @private
-        */
-
-       /**
-        * @ignore
         * @param {Object|undefined} store The Storage instance to wrap around
         */
        function SafeStorage( store ) {
@@ -75,6 +71,9 @@
        };
 
        /**
+        * A wrapper for the HTML5 `localStorage` interface
+        * that is safe to call on all browsers.
+        *
         * @class
         * @singleton
         * @extends mw.SafeStorage
@@ -82,6 +81,9 @@
        mw.storage = new SafeStorage( localStorage );
 
        /**
+        * A wrapper for the HTML5 `sessionStorage` interface
+        * that is safe to call on all browsers.
+        *
         * @class
         * @singleton
         * @extends mw.SafeStorage
index 20818d2..deb280a 100644 (file)
@@ -6,11 +6,19 @@
 
 /* global mw, $VARS, $CODE */
 
-// eslint-disable-next-line no-unused-vars
-var mediaWikiLoadStart = ( new Date() ).getTime(),
-       mwPerformance = ( window.performance && performance.mark ) ? performance : {
+var mwPerformance = ( window.performance && performance.mark ) ? performance : {
                mark: function () {}
-       };
+       },
+       // Define now() here to ensure valid comparison with mediaWikiLoadEnd (T153819).
+       mwNow = ( function () {
+               var perf = window.performance,
+                       navStart = perf && perf.timing && perf.timing.navigationStart;
+               return navStart && typeof perf.now === 'function' ?
+                       function () { return navStart + perf.now(); } :
+                       function () { return +new Date(); };
+       }() ),
+       // eslint-disable-next-line no-unused-vars
+       mediaWikiLoadStart = mwNow();
 
 mwPerformance.mark( 'mwLoadStart' );
 
index d2494da..50f851c 100644 (file)
@@ -181,22 +181,63 @@ class OutputPageTest extends MediaWikiTestCase {
                $baseDir = dirname( __DIR__ ) . '/data/media';
                return [
                        // File that matches basePath, and exists. Hash found and appended.
-                       [ 'baseDir' => $baseDir, 'basePath' => '/w', '/w/test.jpg', '/w/test.jpg?edcf2' ],
+                       [
+                               'baseDir' => $baseDir, 'basePath' => '/w',
+                               '/w/test.jpg',
+                               '/w/test.jpg?edcf2'
+                       ],
                        // File that matches basePath, but not found on disk. Empty query.
-                       [ 'baseDir' => $baseDir, 'basePath' => '/w', '/w/unknown.png', '/w/unknown.png?' ],
+                       [
+                               'baseDir' => $baseDir, 'basePath' => '/w',
+                               '/w/unknown.png',
+                               '/w/unknown.png?'
+                       ],
                        // File not matching basePath. Ignored.
-                       [ 'baseDir' => $baseDir, 'basePath' => '/w', '/files/test.jpg' ],
+                       [
+                               'baseDir' => $baseDir, 'basePath' => '/w',
+                               '/files/test.jpg'
+                       ],
                        // Empty string. Ignored.
-                       [ 'baseDir' => $baseDir, 'basePath' => '/w', '', '' ],
+                       [
+                               'baseDir' => $baseDir, 'basePath' => '/w',
+                               '',
+                               ''
+                       ],
                        // Similar path, but with domain component. Ignored.
-                       [ 'baseDir' => $baseDir, 'basePath' => '/w', '//example.org/w/test.jpg' ],
-                       [ 'baseDir' => $baseDir, 'basePath' => '/w', 'https://example.org/w/test.jpg' ],
+                       [
+                               'baseDir' => $baseDir, 'basePath' => '/w',
+                               '//example.org/w/test.jpg'
+                       ],
+                       [
+                               'baseDir' => $baseDir, 'basePath' => '/w',
+                               'https://example.org/w/test.jpg'
+                       ],
                        // Unrelated path with domain component. Ignored.
-                       [ 'baseDir' => $baseDir, 'basePath' => '/w', 'https://example.org/files/test.jpg' ],
-                       [ 'baseDir' => $baseDir, 'basePath' => '/w', '//example.org/files/test.jpg' ],
+                       [
+                               'baseDir' => $baseDir, 'basePath' => '/w',
+                               'https://example.org/files/test.jpg'
+                       ],
+                       [
+                               'baseDir' => $baseDir, 'basePath' => '/w',
+                               '//example.org/files/test.jpg'
+                       ],
                        // Unrelated path with domain, and empty base path (root mw install). Ignored.
-                       [ 'baseDir' => $baseDir, 'basePath' => '', 'https://example.org/files/test.jpg' ],
-                       [ 'baseDir' => $baseDir, 'basePath' => '', '//example.org/files/test.jpg' ], // T155310
+                       [
+                               'baseDir' => $baseDir, 'basePath' => '',
+                               'https://example.org/files/test.jpg'
+                       ],
+                       [
+                               'baseDir' => $baseDir, 'basePath' => '',
+                               // T155310
+                               '//example.org/files/test.jpg'
+                       ],
+                       // Check UploadPath before ResourceBasePath (T155146)
+                       [
+                               'baseDir' => dirname( $baseDir ), 'basePath' => '',
+                               'uploadDir' => $baseDir, 'uploadPath' => '/images',
+                               '/images/test.jpg',
+                               '/images/test.jpg?edcf2'
+                       ],
                ];
        }
 
@@ -205,9 +246,22 @@ class OutputPageTest extends MediaWikiTestCase {
         * @covers OutputPage::transformFilePath
         * @covers OutputPage::transformResourcePath
         */
-       public function testTransformResourcePath( $baseDir, $basePath, $path, $expected = null ) {
+       public function testTransformResourcePath( $baseDir, $basePath, $uploadDir = null,
+               $uploadPath = null, $path = null, $expected = null
+       ) {
+               if ( $path === null ) {
+                       // Skip optional $uploadDir and $uploadPath
+                       $path = $uploadDir;
+                       $expected = $uploadPath;
+                       $uploadDir = "$baseDir/images";
+                       $uploadPath = "$basePath/images";
+               }
                $this->setMwGlobals( 'IP', $baseDir );
-               $conf = new HashConfig( [ 'ResourceBasePath' => $basePath ] );
+               $conf = new HashConfig( [
+                       'ResourceBasePath' => $basePath,
+                       'UploadDirectory' => $uploadDir,
+                       'UploadPath' => $uploadPath,
+               ] );
 
                MediaWiki\suppressWarnings();
                $actual = OutputPage::transformResourcePath( $conf, $path );
index 1524b78..f54170b 100644 (file)
@@ -27,6 +27,7 @@
 
 use Wikimedia\Rdbms\TransactionProfiler;
 use Wikimedia\Rdbms\DatabaseDomain;
+use Wikimedia\Rdbms\MySQLMasterPos;
 
 /**
  * Fake class around abstract class so we can call concrete methods.
index 172d686..10bf028 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Wikimedia\Rdbms\Blob;
+
 class DatabaseSqliteMock extends DatabaseSqlite {
        private $lastQuery;
 
index 92c8add..4b857ce 100644 (file)
@@ -3,6 +3,7 @@
 use Wikimedia\Rdbms\LBFactorySimple;
 use Wikimedia\Rdbms\LBFactoryMulti;
 use Wikimedia\Rdbms\ChronologyProtector;
+use Wikimedia\Rdbms\MySQLMasterPos;
 
 /**
  * Holds tests for LBFactory abstract MediaWiki class.
index 998817d..49a5b18 100644 (file)
                                group1: {
                                        title: 'Group 1',
                                        type: 'send_unselected_if_any',
-                                       exclusionType: 'default',
                                        filters: [
                                                {
                                                        name: 'hidefilter1',
                                        ]
                                }
                        },
+                       defaultFilterRepresentation = {
+                               // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
+                               hidefilter1: false,
+                               hidefilter2: true,
+                               hidefilter3: false,
+                               hidefilter4: true,
+                               hidefilter5: false,
+                               hidefilter6: true
+                       },
                        model = new mw.rcfilters.dm.FiltersViewModel();
 
                model.initializeFilters( definition );
 
                assert.deepEqual(
-                       model.getFullState(),
+                       model.getSelectedState(),
                        {
-                               // Group 1
-                               hidefilter1: { selected: true, active: true },
-                               hidefilter2: { selected: false, active: true },
-                               hidefilter3: { selected: true, active: true },
-                               // Group 2
-                               hidefilter4: { selected: false, active: true },
-                               hidefilter5: { selected: true, active: true },
-                               hidefilter6: { selected: false, active: true },
+                               hidefilter1: false,
+                               hidefilter2: false,
+                               hidefilter3: false,
+                               hidefilter4: false,
+                               hidefilter5: false,
+                               hidefilter6: false
                        },
-                       'Initial state: all filters are active, and select states are default.'
+                       'Initial state: default filters are not selected (controller selects defaults explicitly).'
                );
 
-               // Default behavior for 'exclusion' type with only 1 item selected, means that:
-               // - The items in the same group that are *not* selected are *not* active
-               // - Items in other groups are unaffected (all active)
                model.updateFilters( {
                        hidefilter1: false,
-                       hidefilter2: false,
-                       hidefilter3: false,
-                       hidefilter4: false,
-                       hidefilter5: false,
-                       hidefilter6: true
+                       hidefilter3: false
                } );
-               assert.deepEqual(
-                       model.getFullState(),
-                       {
-                               // Group 1: not affected
-                               hidefilter1: { selected: false, active: true },
-                               hidefilter2: { selected: false, active: true },
-                               hidefilter3: { selected: false, active: true },
-                               // Group 2: affected
-                               hidefilter4: { selected: false, active: false },
-                               hidefilter5: { selected: false, active: false },
-                               hidefilter6: { selected: true, active: true },
-                       },
-                       'Default exclusion behavior with 1 item selected in the group.'
-               );
 
-               // Default behavior for 'exclusion' type with multiple items selected, but not all, means that:
-               // - The items in the same group that are *not* selected are *not* active
-               // - Items in other groups are unaffected (all active)
-               model.updateFilters( {
-                       // Literally updating filters to create a clean state
-                       hidefilter1: false,
-                       hidefilter2: false,
-                       hidefilter3: false,
-                       hidefilter4: false,
-                       hidefilter5: true,
-                       hidefilter6: true
-               } );
-               assert.deepEqual(
-                       model.getFullState(),
-                       {
-                               // Group 1: not affected
-                               hidefilter1: { selected: false, active: true },
-                               hidefilter2: { selected: false, active: true },
-                               hidefilter3: { selected: false, active: true },
-                               // Group 2: affected
-                               hidefilter4: { selected: false, active: false },
-                               hidefilter5: { selected: true, active: true },
-                               hidefilter6: { selected: true, active: true },
-                       },
-                       'Default exclusion behavior with multiple items (but not all) selected in the group.'
-               );
+               model.setFiltersToDefaults();
 
-               // Default behavior for 'exclusion' type with all items in the group selected, means that:
-               // - All items in the group are NOT active
-               // - Items in other groups are unaffected (all active)
-               model.updateFilters( {
-                       // Literally updating filters to create a clean state
-                       hidefilter1: false,
-                       hidefilter2: false,
-                       hidefilter3: false,
-                       hidefilter4: true,
-                       hidefilter5: true,
-                       hidefilter6: true
-               } );
                assert.deepEqual(
-                       model.getFullState(),
-                       {
-                               // Group 1: not affected
-                               hidefilter1: { selected: false, active: true },
-                               hidefilter2: { selected: false, active: true },
-                               hidefilter3: { selected: false, active: true },
-                               // Group 2: affected
-                               hidefilter4: { selected: true, active: false },
-                               hidefilter5: { selected: true, active: false },
-                               hidefilter6: { selected: true, active: false },
-                       },
-                       'Default exclusion behavior with all items in the group.'
+                       model.getSelectedState(),
+                       defaultFilterRepresentation,
+                       'Changing values of filters and then returning to defaults still results in default filters being selected.'
                );
        } );
 
-       QUnit.test( 'reapplyActiveFilters - "explicit" exclusion rules', function ( assert ) {
+       QUnit.test( 'Filter interaction: subsets', function ( assert ) {
                var definition = {
                                group1: {
                                        title: 'Group 1',
-                                       type: 'send_unselected_if_any',
-                                       exclusionType: 'explicit',
+                                       type: 'string_options',
                                        filters: [
                                                {
                                                        name: 'filter1',
-                                                       excludes: [ 'filter2', 'filter3' ],
                                                        label: 'Show filter 1',
-                                                       description: 'Description of Filter 1 in Group 1'
+                                                       description: 'Description of Filter 1 in Group 1',
+                                                       subset: [ 'filter2', 'filter5' ]
                                                },
                                                {
                                                        name: 'filter2',
-                                                       excludes: [ 'filter3' ],
                                                        label: 'Show filter 2',
                                                        description: 'Description of Filter 2 in Group 1'
                                                },
                                                {
                                                        name: 'filter3',
                                                        label: 'Show filter 3',
-                                                       excludes: [ 'filter1' ],
                                                        description: 'Description of Filter 3 in Group 1'
-                                               },
+                                               }
+                                       ]
+                               },
+                               group2: {
+                                       title: 'Group 2',
+                                       type: 'send_unselected_if_any',
+                                       filters: [
                                                {
                                                        name: 'filter4',
                                                        label: 'Show filter 4',
-                                                       description: 'Description of Filter 4 in Group 1'
+                                                       description: 'Description of Filter 1 in Group 2',
+                                                       subset: [ 'filter3', 'filter5' ]
+                                               },
+                                               {
+                                                       name: 'filter5',
+                                                       label: 'Show filter 5',
+                                                       description: 'Description of Filter 2 in Group 2'
+                                               },
+                                               {
+                                                       name: 'filter6',
+                                                       label: 'Show filter 6',
+                                                       description: 'Description of Filter 3 in Group 2'
                                                }
                                        ]
                                }
                        },
-                       defaultFilterRepresentation = {
-                               // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
-                               hidefilter1: false,
-                               hidefilter2: true,
-                               hidefilter3: false,
-                               hidefilter4: true,
-                               hidefilter5: false,
-                               hidefilter6: true,
-                               // Group 3, "string_options", default values correspond to parameters and filters
-                               filter7: false,
-                               filter8: true,
-                               filter9: false
+                       baseFullState = {
+                               filter1: { selected: false, conflicted: false, included: false },
+                               filter2: { selected: false, conflicted: false, included: false },
+                               filter3: { selected: false, conflicted: false, included: false },
+                               filter4: { selected: false, conflicted: false, included: false },
+                               filter5: { selected: false, conflicted: false, included: false },
+                               filter6: { selected: false, conflicted: false, included: false }
                        },
                        model = new mw.rcfilters.dm.FiltersViewModel();
 
                model.initializeFilters( definition );
+               // Select a filter that has subset with another filter
+               model.updateFilters( {
+                       filter1: true
+               } );
 
+               model.reassessFilterInteractions( model.getItemByName( 'filter1' ) );
                assert.deepEqual(
                        model.getFullState(),
-                       {
-                               filter1: { selected: false, active: true },
-                               filter2: { selected: false, active: true },
-                               filter3: { selected: false, active: true },
-                               filter4: { selected: false, active: true }
-                       },
-                       'Initial state: all filters are active.'
+                       $.extend( true, {}, baseFullState, {
+                               filter1: { selected: true },
+                               filter2: { included: true },
+                               filter5: { included: true }
+                       } ),
+                       'Filters with subsets are represented in the model.'
                );
 
-               // "Explicit" behavior for 'exclusion' with one item checked:
-               // - Items in the 'excluded' list of the selected filter are inactive
+               // Select another filter that has a subset with the same previous filter
                model.updateFilters( {
-                       // Literally updating filters to create a clean state
-                       filter1: true, // Excludes 'hidefilter2', 'hidefilter3'
-                       filter2: false, // Excludes 'hidefilter3'
-                       filter3: false, // Excludes 'hidefilter1'
-                       filter4: false // No exclusion list
+                       filter4: true
                } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter4' ) );
                assert.deepEqual(
                        model.getFullState(),
-                       {
-                               filter1: { selected: true, active: true },
-                               filter2: { selected: false, active: false },
-                               filter3: { selected: false, active: false },
-                               filter4: { selected: false, active: true }
-                       },
-                       '"Explicit" exclusion behavior with one item selected that has an exclusion list.'
+                       $.extend( true, {}, baseFullState, {
+                               filter1: { selected: true },
+                               filter2: { included: true },
+                               filter3: { included: true },
+                               filter4: { selected: true },
+                               filter5: { included: true }
+                       } ),
+                       'Filters that have multiple subsets are represented.'
                );
 
-               // "Explicit" behavior for 'exclusion' with two item checked:
-               // - Items in the 'excluded' list of each of the selected filter are inactive
+               // Remove one filter (but leave the other) that affects filter2
                model.updateFilters( {
-                       // Literally updating filters to create a clean state
-                       filter1: true, // Excludes 'hidefilter2', 'hidefilter3'
-                       filter2: false, // Excludes 'hidefilter3'
-                       filter3: true, // Excludes 'hidefilter1'
-                       filter4: false // No exclusion list
+                       filter1: false
                } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter1' ) );
                assert.deepEqual(
                        model.getFullState(),
-                       {
-                               filter1: { selected: true, active: false },
-                               filter2: { selected: false, active: false },
-                               filter3: { selected: true, active: false },
-                               filter4: { selected: false, active: true }
+                       $.extend( true, {}, baseFullState, {
+                               filter2: { included: false },
+                               filter3: { included: true },
+                               filter4: { selected: true },
+                               filter5: { included: true }
+                       } ),
+                       'Removing a filter only un-includes its subset if there is no other filter affecting.'
+               );
+
+               model.updateFilters( {
+                       filter4: false
+               } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter4' ) );
+               assert.deepEqual(
+                       model.getFullState(),
+                       baseFullState,
+                       'Removing all supersets also un-includes the subsets.'
+               );
+       } );
+
+       QUnit.test( 'Filter interaction: full coverage', function ( assert ) {
+               var definition = {
+                               group1: {
+                                       title: 'Group 1',
+                                       type: 'string_options',
+                                       fullCoverage: false,
+                                       filters: [
+                                               { name: 'filter1' },
+                                               { name: 'filter2' },
+                                               { name: 'filter3' },
+                                       ]
+                               },
+                               group2: {
+                                       title: 'Group 2',
+                                       type: 'send_unselected_if_any',
+                                       fullCoverage: true,
+                                       filters: [
+                                               { name: 'filter4' },
+                                               { name: 'filter5' },
+                                               { name: 'filter6' },
+                                       ]
+                               }
                        },
-                       '"Explicit" exclusion behavior with two selected items that both have an exclusion list.'
+                       isCapsuleItemMuted = function ( filterName ) {
+                               var itemModel = model.getItemByName( filterName ),
+                                       groupModel = itemModel.getGroupModel();
+
+                               // This is the logic inside the capsule widget
+                               return (
+                                       // The capsule item widget only appears if the item is selected
+                                       itemModel.isSelected() &&
+                                       // Muted state is only valid if group is full coverage and all items are selected
+                                       groupModel.isFullCoverage() && groupModel.areAllSelected()
+                               );
+                       },
+                       getCurrentItemsMutedState = function () {
+                               return {
+                                       filter1: isCapsuleItemMuted( 'filter1' ),
+                                       filter2: isCapsuleItemMuted( 'filter2' ),
+                                       filter3: isCapsuleItemMuted( 'filter3' ),
+                                       filter4: isCapsuleItemMuted( 'filter4' ),
+                                       filter5: isCapsuleItemMuted( 'filter5' ),
+                                       filter6: isCapsuleItemMuted( 'filter6' )
+                               };
+                       },
+                       baseMuteState = {
+                               filter1: false,
+                               filter2: false,
+                               filter3: false,
+                               filter4: false,
+                               filter5: false,
+                               filter6: false
+                       },
+                       model = new mw.rcfilters.dm.FiltersViewModel();
+
+               model.initializeFilters( definition );
+
+               // Starting state, no selection, all items are non-muted
+               assert.deepEqual(
+                       getCurrentItemsMutedState(),
+                       baseMuteState,
+                       'No selection - all items are non-muted'
+               );
+
+               // Select most (but not all) items in each group
+               model.updateFilters( {
+                       filter1: true,
+                       filter2: true,
+                       filter4: true,
+                       filter5: true
+               } );
+
+               // Both groups have multiple (but not all) items selected, all items are non-muted
+               assert.deepEqual(
+                       getCurrentItemsMutedState(),
+                       baseMuteState,
+                       'Not all items in the group selected - all items are non-muted'
                );
 
-               // "Explicit behavior" with two filters that exclude the same item
+               // Select all items in 'fullCoverage' group (group2)
+               model.updateFilters( {
+                       filter6: true
+               } );
+
+               // Group2 (full coverage) has all items selected, all its items are muted
+               assert.deepEqual(
+                       getCurrentItemsMutedState(),
+                       $.extend( {}, baseMuteState, {
+                               filter4: true,
+                               filter5: true,
+                               filter6: true
+                       } ),
+                       'All items in \'full coverage\' group are selected - all items in the group are muted'
+               );
 
-               // Two filters selected, both exclude 'hidefilter3'
+               // Select all items in non 'fullCoverage' group (group1)
                model.updateFilters( {
-                       // Literally updating filters to create a clean state
-                       filter1: true, // Excludes 'hidefilter2', 'hidefilter3'
-                       filter2: true, // Excludes 'hidefilter3'
-                       filter3: false, // Excludes 'hidefilter1'
-                       filter4: false // No exclusion list
+                       filter3: true
                } );
+
+               // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
                assert.deepEqual(
-                       model.getFullState(),
-                       {
-                               filter1: { selected: true, active: true },
-                               filter2: { selected: true, active: false }, // Excluded by filter1
-                               filter3: { selected: false, active: false }, // Excluded by both filter1 and filter2
-                               filter4: { selected: false, active: true }
+                       getCurrentItemsMutedState(),
+                       $.extend( {}, baseMuteState, {
+                               filter4: true,
+                               filter5: true,
+                               filter6: true
+                       } ),
+                       'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
+               );
+
+               // Uncheck an item from each group
+               model.updateFilters( {
+                       filter3: false,
+                       filter5: false
+               } );
+               assert.deepEqual(
+                       getCurrentItemsMutedState(),
+                       baseMuteState,
+                       'Not all items in the group are checked - all items are non-muted regardless of group coverage'
+               );
+       } );
+
+       QUnit.test( 'Filter interaction: conflicts', function ( assert ) {
+               var definition = {
+                               group1: {
+                                       title: 'Group 1',
+                                       type: 'string_options',
+                                       filters: [
+                                               {
+                                                       name: 'filter1',
+                                                       conflicts: [ 'filter2', 'filter4' ]
+                                               },
+                                               {
+                                                       name: 'filter2',
+                                                       conflicts: [ 'filter6' ]
+                                               },
+                                               {
+                                                       name: 'filter3'
+                                               }
+                                       ]
+                               },
+                               group2: {
+                                       title: 'Group 2',
+                                       type: 'send_unselected_if_any',
+                                       filters: [
+                                               {
+                                                       name: 'filter4'
+                                               },
+                                               {
+                                                       name: 'filter5',
+                                                       conflicts: [ 'filter3' ]
+                                               },
+                                               {
+                                                       name: 'filter6',
+                                               }
+                                       ]
+                               }
                        },
-                       '"Explicit" exclusion behavior with two selected items that both exclude another item.'
+                       baseFullState = {
+                               filter1: { selected: false, conflicted: false, included: false },
+                               filter2: { selected: false, conflicted: false, included: false },
+                               filter3: { selected: false, conflicted: false, included: false },
+                               filter4: { selected: false, conflicted: false, included: false },
+                               filter5: { selected: false, conflicted: false, included: false },
+                               filter6: { selected: false, conflicted: false, included: false }
+                       },
+                       model = new mw.rcfilters.dm.FiltersViewModel();
+
+               model.initializeFilters( definition );
+
+               assert.deepEqual(
+                       model.getFullState(),
+                       baseFullState,
+                       'Initial state: no conflicts because no selections.'
                );
 
-               // Unselect filter2: filter3 should still be excluded, because filter1 excludes it and is selected
+               // Select a filter that has a conflict with another
                model.updateFilters( {
-                       filter2: false, // Excludes 'hidefilter3'
+                       filter1: true // conflicts: filter2, filter4
                } );
+
+               model.reassessFilterInteractions( model.getItemByName( 'filter1' ) );
+
                assert.deepEqual(
                        model.getFullState(),
-                       {
-                               filter1: { selected: true, active: true },
-                               filter2: { selected: false, active: false }, // Excluded by filter1
-                               filter3: { selected: false, active: false }, // Still excluded by filter1
-                               filter4: { selected: false, active: true }
-                       },
-                       '"Explicit" exclusion behavior unselecting one item that excludes another item, that is being excluded by a third active item.'
+                       $.extend( true, {}, baseFullState, {
+                               filter1: { selected: true },
+                               filter2: { conflicted: true },
+                               filter4: { conflicted: true },
+                       } ),
+                       'Selecting a filter set its conflicts list as "conflicted".'
                );
 
-               // Unselect filter1: filter3 should now be active, since both filters that exclude it are unselected
+               // Select one of the conflicts (both filters are now conflicted and selected)
                model.updateFilters( {
-                       filter1: false, // Excludes 'hidefilter3' and 'hidefilter2'
+                       filter4: true // conflicts: filter 1
                } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter4' ) );
+
                assert.deepEqual(
                        model.getFullState(),
-                       {
-                               filter1: { selected: false, active: true },
-                               filter2: { selected: false, active: true }, // No longer excluded by filter1
-                               filter3: { selected: false, active: true }, // No longer excluded by either filter1 nor filter2
-                               filter4: { selected: false, active: true }
-                       },
-                       '"Explicit" exclusion behavior unselecting both items that excluded the same third item.'
+                       $.extend( true, {}, baseFullState, {
+                               filter1: { selected: true, conflicted: true },
+                               filter2: { conflicted: true },
+                               filter4: { selected: true, conflicted: true },
+                       } ),
+                       'Selecting a conflicting filter sets both sides to conflicted and selected.'
                );
 
+               // Select another filter from filter4 group, meaning:
+               // now filter1 no longer conflicts with filter4
+               model.updateFilters( {
+                       filter6: true // conflicts: filter2
+               } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter6' ) );
+
+               assert.deepEqual(
+                       model.getFullState(),
+                       $.extend( true, {}, baseFullState, {
+                               filter1: { selected: true, conflicted: false }, // No longer conflicts (filter4 is not the only in the group)
+                               filter2: { conflicted: true }, // While not selected, still in conflict with filter1, which is selected
+                               filter4: { selected: true, conflicted: false }, // No longer conflicts with filter1
+                               filter6: { selected: true, conflicted: false }
+                       } ),
+                       'Selecting a non-conflicting filter from a conflicting group removes the conflict'
+               );
        } );
 }( mediaWiki, jQuery ) );
index bac8274..65b7263 100644 (file)
                );
        } );
 
+       QUnit.test( 'mw.now', function ( assert ) {
+               assert.equal( typeof mw.now(), 'number', 'Return a number' );
+               assert.equal(
+                       String( Math.round( mw.now() ) ).length,
+                       String( +new Date() ).length,
+                       'Match size of current timestamp'
+               );
+       } );
+
        QUnit.test( 'mw.Map', function ( assert ) {
                var arry, conf, funky, globalConf, nummy, someValues;