From: jenkins-bot Date: Fri, 27 Sep 2019 00:13:30 +0000 (+0000) Subject: Merge "filebackend: avoid use of wfWikiId() in FileBackendGroup" X-Git-Tag: 1.34.0-rc.0~81 X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=commitdiff_plain;h=512b7cd8d73facec6b69047eedf102ae2c38c3d6;hp=a775c6888bdecd03cea73f5540a8323b6dcbe78f Merge "filebackend: avoid use of wfWikiId() in FileBackendGroup" --- diff --git a/.pipeline/blubber.yaml b/.pipeline/blubber.yaml new file mode 100644 index 0000000000..13ad966774 --- /dev/null +++ b/.pipeline/blubber.yaml @@ -0,0 +1,14 @@ +version: v4 +base: docker-registry.wikimedia.org/dev/stretch-php72-fpm-apache2 + +lives: + in: /var/www/html + +variants: + dev: + runs: + insecurely: true + builder: + command: [.pipeline/dev_prereq.sh] + requirements: [.pipeline, .pipeline/dev_prereq.sh, composer.json] + copies: [local] diff --git a/.pipeline/config.yaml b/.pipeline/config.yaml new file mode 100644 index 0000000000..08f1db86e4 --- /dev/null +++ b/.pipeline/config.yaml @@ -0,0 +1,9 @@ +pipelines: + publish: + blubberfile: blubber.yaml + stages: + - name: dev + build: dev + publish: + image: true + tags: [dev] diff --git a/.pipeline/dev_prereq.sh b/.pipeline/dev_prereq.sh new file mode 100755 index 0000000000..a1f4bd0160 --- /dev/null +++ b/.pipeline/dev_prereq.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +mkdir /tmp/php +mkdir -p extensions + +git clone --depth 1 https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor.git /var/www/html/extensions/VisualEditor +git clone --depth 1 https://gerrit.wikimedia.org/r/mediawiki/skins/Vector /var/www/html/skins/Vector +cd /var/www/html/extensions/VisualEditor +git submodule update --depth 1 --init + +cd /var/www/html +composer install +cat < LocalSettings.php +stage = $migrationStage; + public function __construct( Language $lang, $stage ) { + if ( ( $stage & SCHEMA_COMPAT_WRITE_BOTH ) === 0 ) { + throw new InvalidArgumentException( '$stage must include a write mode' ); + } + if ( ( $stage & SCHEMA_COMPAT_READ_BOTH ) === 0 ) { + throw new InvalidArgumentException( '$stage must include a read mode' ); + } + + $this->stage = $stage; $this->lang = $lang; } @@ -166,21 +175,21 @@ class CommentStore { public function getFields( $key = null ) { $key = $this->getKey( $key ); $fields = []; - if ( $this->stage === MIGRATION_OLD ) { + if ( ( $this->stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD ) { $fields["{$key}_text"] = $key; $fields["{$key}_data"] = 'NULL'; $fields["{$key}_cid"] = 'NULL'; - } else { - if ( $this->stage < MIGRATION_NEW ) { + } else { // READ_BOTH or READ_NEW + if ( $this->stage & SCHEMA_COMPAT_READ_OLD ) { $fields["{$key}_old"] = $key; } $tempTableStage = isset( $this->tempTables[$key] ) ? $this->tempTables[$key]['stage'] : MIGRATION_NEW; - if ( $tempTableStage < MIGRATION_NEW ) { + if ( $tempTableStage & SCHEMA_COMPAT_READ_OLD ) { $fields["{$key}_pk"] = $this->tempTables[$key]['joinPK']; } - if ( $tempTableStage > MIGRATION_OLD ) { + if ( $tempTableStage & SCHEMA_COMPAT_READ_NEW ) { $fields["{$key}_id"] = "{$key}_id"; } } @@ -211,21 +220,21 @@ class CommentStore { $fields = []; $joins = []; - if ( $this->stage === MIGRATION_OLD ) { + if ( ( $this->stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD ) { $fields["{$key}_text"] = $key; $fields["{$key}_data"] = 'NULL'; $fields["{$key}_cid"] = 'NULL'; - } else { - $join = $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN'; + } else { // READ_BOTH or READ_NEW + $join = ( $this->stage & SCHEMA_COMPAT_READ_OLD ) ? 'LEFT JOIN' : 'JOIN'; $tempTableStage = isset( $this->tempTables[$key] ) ? $this->tempTables[$key]['stage'] : MIGRATION_NEW; - if ( $tempTableStage < MIGRATION_NEW ) { + if ( $tempTableStage & SCHEMA_COMPAT_READ_OLD ) { $t = $this->tempTables[$key]; $alias = "temp_$key"; $tables[$alias] = $t['table']; $joins[$alias] = [ $join, "{$alias}.{$t['pk']} = {$t['joinPK']}" ]; - if ( $tempTableStage === MIGRATION_OLD ) { + if ( ( $tempTableStage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD ) { $joinField = "{$alias}.{$t['field']}"; } else { // Nothing hits this code path for now, but will in the future when we set @@ -245,7 +254,7 @@ class CommentStore { $tables[$alias] = 'comment'; $joins[$alias] = [ $join, "{$alias}.comment_id = {$joinField}" ]; - if ( $this->stage === MIGRATION_NEW ) { + if ( ( $this->stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_NEW ) { $fields["{$key}_text"] = "{$alias}.comment_text"; } else { $fields["{$key}_text"] = "COALESCE( {$alias}.comment_text, $key )"; @@ -282,13 +291,15 @@ class CommentStore { $cid = $row["{$key}_cid"] ?? null; $text = $row["{$key}_text"]; $data = $row["{$key}_data"]; - } elseif ( $this->stage === MIGRATION_OLD ) { + } elseif ( ( $this->stage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD ) { $cid = null; if ( $fallback && isset( $row[$key] ) ) { wfLogWarning( "Using deprecated fallback handling for comment $key" ); $text = $row[$key]; } else { - wfLogWarning( "Missing {$key}_text and {$key}_data fields in row with MIGRATION_OLD" ); + wfLogWarning( + "Missing {$key}_text and {$key}_data fields in row with MIGRATION_OLD / READ_OLD" + ); $text = ''; } $data = null; @@ -296,7 +307,7 @@ class CommentStore { $tempTableStage = isset( $this->tempTables[$key] ) ? $this->tempTables[$key]['stage'] : MIGRATION_NEW; $row2 = null; - if ( $tempTableStage > MIGRATION_OLD && array_key_exists( "{$key}_id", $row ) ) { + if ( ( $tempTableStage & SCHEMA_COMPAT_READ_NEW ) && array_key_exists( "{$key}_id", $row ) ) { if ( !$db ) { throw new InvalidArgumentException( "\$row does not contain fields needed for comment $key and getComment(), but " @@ -311,7 +322,9 @@ class CommentStore { __METHOD__ ); } - if ( !$row2 && $tempTableStage < MIGRATION_NEW && array_key_exists( "{$key}_pk", $row ) ) { + if ( !$row2 && ( $tempTableStage & SCHEMA_COMPAT_READ_OLD ) && + array_key_exists( "{$key}_pk", $row ) + ) { if ( !$db ) { throw new InvalidArgumentException( "\$row does not contain fields needed for comment $key and getComment(), but " @@ -341,7 +354,9 @@ class CommentStore { $cid = $row2->comment_id; $text = $row2->comment_text; $data = $row2->comment_data; - } elseif ( $this->stage < MIGRATION_NEW && array_key_exists( "{$key}_old", $row ) ) { + } elseif ( ( $this->stage & SCHEMA_COMPAT_READ_OLD ) && + array_key_exists( "{$key}_old", $row ) + ) { $cid = null; $text = $row["{$key}_old"]; $data = null; @@ -474,7 +489,7 @@ class CommentStore { # Truncate comment in a Unicode-sensitive manner $comment->text = $this->lang->truncateForVisual( $comment->text, self::COMMENT_CHARACTER_LIMIT ); - if ( $this->stage > MIGRATION_OLD && !$comment->id ) { + if ( ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) && !$comment->id ) { $dbData = $comment->data; if ( !$comment->message instanceof RawMessage ) { if ( $dbData === null ) { @@ -534,14 +549,14 @@ class CommentStore { $comment = $this->createComment( $dbw, $comment, $data ); - if ( $this->stage <= MIGRATION_WRITE_BOTH ) { + if ( $this->stage & SCHEMA_COMPAT_WRITE_OLD ) { $fields[$key] = $this->lang->truncateForDatabase( $comment->text, 255 ); } - if ( $this->stage >= MIGRATION_WRITE_BOTH ) { + if ( $this->stage & SCHEMA_COMPAT_WRITE_NEW ) { $tempTableStage = isset( $this->tempTables[$key] ) ? $this->tempTables[$key]['stage'] : MIGRATION_NEW; - if ( $tempTableStage <= MIGRATION_WRITE_BOTH ) { + if ( $tempTableStage & SCHEMA_COMPAT_WRITE_OLD ) { $t = $this->tempTables[$key]; $func = __METHOD__; $commentId = $comment->id; @@ -556,7 +571,7 @@ class CommentStore { ); }; } - if ( $tempTableStage >= MIGRATION_WRITE_BOTH ) { + if ( $tempTableStage & SCHEMA_COMPAT_WRITE_NEW ) { $fields["{$key}_id"] = $comment->id; } } @@ -594,7 +609,7 @@ class CommentStore { $tempTableStage = isset( $this->tempTables[$key] ) ? $this->tempTables[$key]['stage'] : MIGRATION_NEW; - if ( $tempTableStage < MIGRATION_WRITE_NEW ) { + if ( $tempTableStage & SCHEMA_COMPAT_WRITE_OLD ) { throw new InvalidArgumentException( "Must use insertWithTempTable() for $key" ); } diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 29b628cd40..31cb7ae37c 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -7937,6 +7937,7 @@ $wgAllowSpecialInclusion = true; /** * Set this to an array of special page names to prevent * maintenance/updateSpecialPages.php from updating those pages. + * Mapping each special page name to an run mode like 'periodical' if a cronjob is set up. */ $wgDisableQueryPageUpdate = false; diff --git a/includes/Html.php b/includes/Html.php index a8f349606c..ea2ce07a32 100644 --- a/includes/Html.php +++ b/includes/Html.php @@ -1011,7 +1011,7 @@ class Html { /** * Get HTML for an information message box with an icon. * - * @internal For use by the WebInstaller class. + * @internal For use by the WebInstaller class only. * @param string $rawHtml HTML * @param string $icon Path to icon file (used as 'src' attribute) * @param string $alt Alternate text for the icon diff --git a/includes/MediaWiki.php b/includes/MediaWiki.php index 28c9e16002..1a75714fbc 100644 --- a/includes/MediaWiki.php +++ b/includes/MediaWiki.php @@ -526,11 +526,15 @@ class MediaWiki { try { $this->main(); } catch ( ErrorPageError $e ) { + $out = $this->context->getOutput(); + // TODO: Should ErrorPageError::report accept a OutputPage parameter? + $e->report( ErrorPageError::STAGE_OUTPUT ); + // T64091: while exceptions are convenient to bubble up GUI errors, // they are not internal application faults. As with normal requests, this // should commit, print the output, do deferred updates, jobs, and profiling. $this->doPreOutputCommit(); - $e->report(); // display the GUI error + $out->output(); // display the GUI error } } catch ( Exception $e ) { $context = $this->context; diff --git a/includes/Rest/Router.php b/includes/Rest/Router.php index 6821d89c27..6dfcf3c2d7 100644 --- a/includes/Rest/Router.php +++ b/includes/Rest/Router.php @@ -276,8 +276,7 @@ class Router { $request->setPathParams( array_map( 'rawurldecode', $match['params'] ) ); $spec = $match['userData']; $objectFactorySpec = array_intersect_key( $spec, - // @todo ObjectFactory supports more keys than this. - [ 'factory' => true, 'class' => true, 'args' => true ] ); + [ 'factory' => true, 'class' => true, 'args' => true, 'services' => true ] ); /** @var $handler Handler (annotation for PHPStorm) */ $handler = $this->objectFactory->createObject( $objectFactorySpec ); $handler->init( $this, $request, $spec, $this->responseFactory ); diff --git a/includes/Revision/RevisionStore.php b/includes/Revision/RevisionStore.php index a5cf8404cb..a1aeccb297 100644 --- a/includes/Revision/RevisionStore.php +++ b/includes/Revision/RevisionStore.php @@ -1928,7 +1928,10 @@ class RevisionStore $titlesByPageId = []; foreach ( $rows as $row ) { if ( isset( $rowsByRevId[$row->rev_id] ) ) { - throw new InvalidArgumentException( "Duplicate rows in newRevisionsFromBatch {$row->rev_id}" ); + $result->warning( + 'internalerror', + "Duplicate rows in newRevisionsFromBatch, rev_id {$row->rev_id}" + ); } if ( $title && $row->rev_page != $title->getArticleID() ) { throw new InvalidArgumentException( diff --git a/includes/api/ApiQueryBacklinksprop.php b/includes/api/ApiQueryBacklinksprop.php index b8672ee6b7..022fd9b848 100644 --- a/includes/api/ApiQueryBacklinksprop.php +++ b/includes/api/ApiQueryBacklinksprop.php @@ -286,6 +286,8 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase { $res = $this->select( __METHOD__ ); if ( is_null( $resultPageSet ) ) { + $this->executeGenderCacheFromResultWrapper( $res, __METHOD__ ); + $count = 0; foreach ( $res as $row ) { if ( ++$count > $params['limit'] ) { diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index 8d9cb48c1a..059c438a37 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -20,6 +20,7 @@ * @file */ +use MediaWiki\MediaWikiServices; use Wikimedia\Rdbms\IDatabase; use Wikimedia\Rdbms\IResultWrapper; @@ -569,6 +570,42 @@ abstract class ApiQueryBase extends ApiBase { ); } + /** + * Preprocess the result set to fill the GenderCache with the necessary information + * before using self::addTitleInfo + * + * @param IResultWrapper $res Result set to work on. + * The result set must have _namespace and _title fields with the provided field prefix + * @param string $fname The caller function name, always use __METHOD__ + * @param string $fieldPrefix Prefix for fields to check gender for + */ + protected function executeGenderCacheFromResultWrapper( + IResultWrapper $res, $fname = __METHOD__, $fieldPrefix = 'page' + ) { + if ( !$res->numRows() ) { + return; + } + + $services = MediaWikiServices::getInstance(); + $nsInfo = $services->getNamespaceInfo(); + $namespaceField = $fieldPrefix . '_namespace'; + $titleField = $fieldPrefix . '_title'; + + $usernames = []; + foreach ( $res as $row ) { + if ( $nsInfo->hasGenderDistinction( $row->$namespaceField ) ) { + $usernames[] = $row->$titleField; + } + } + + if ( $usernames === [] ) { + return; + } + + $genderCache = $services->getGenderCache(); + $genderCache->doQuery( $usernames, $fname ); + } + /** @} */ /************************************************************************//** diff --git a/includes/db/MWLBFactory.php b/includes/db/MWLBFactory.php index 18030090df..ad5708a2f2 100644 --- a/includes/db/MWLBFactory.php +++ b/includes/db/MWLBFactory.php @@ -381,7 +381,6 @@ abstract class MWLBFactory { * T154872). */ $lbFactory->setIndexAliases( [ - 'ar_usertext_timestamp' => 'usertext_timestamp', 'un_user_id' => 'user_id', 'un_user_ip' => 'user_ip', ] ); diff --git a/includes/exception/BadRequestError.php b/includes/exception/BadRequestError.php index 5fcf0e6217..2448421afd 100644 --- a/includes/exception/BadRequestError.php +++ b/includes/exception/BadRequestError.php @@ -26,9 +26,9 @@ */ class BadRequestError extends ErrorPageError { - public function report() { + public function report( $action = self::SEND_OUTPUT ) { global $wgOut; $wgOut->setStatusCode( 400 ); - parent::report(); + parent::report( $action ); } } diff --git a/includes/exception/ErrorPageError.php b/includes/exception/ErrorPageError.php index 4b1812673f..64216a4f4c 100644 --- a/includes/exception/ErrorPageError.php +++ b/includes/exception/ErrorPageError.php @@ -25,6 +25,8 @@ * @ingroup Exception */ class ErrorPageError extends MWException implements ILocalizedException { + const SEND_OUTPUT = 0; + const STAGE_OUTPUT = 1; public $title, $msg, $params; /** @@ -60,13 +62,19 @@ class ErrorPageError extends MWException implements ILocalizedException { return wfMessage( $this->msg, $this->params ); } - public function report() { + public function report( $action = self::SEND_OUTPUT ) { if ( self::isCommandLine() || defined( 'MW_API' ) ) { parent::report(); } else { global $wgOut; $wgOut->showErrorPage( $this->title, $this->msg, $this->params ); - $wgOut->output(); + // Allow skipping of the final output step, so that web-based page views + // from MediaWiki.php, can inspect the staged OutputPage state, and perform + // graceful shutdown via doPreOutputCommit first, just like for regular + // output when there isn't an error page. + if ( $action === self::SEND_OUTPUT ) { + $wgOut->output(); + } } } } diff --git a/includes/exception/PermissionsError.php b/includes/exception/PermissionsError.php index 87a3dc2819..9fa1c7ce29 100644 --- a/includes/exception/PermissionsError.php +++ b/includes/exception/PermissionsError.php @@ -67,10 +67,12 @@ class PermissionsError extends ErrorPageError { parent::__construct( 'permissionserrors', Message::newFromSpecifier( $errors[0] ) ); } - public function report() { + public function report( $action = self::SEND_OUTPUT ) { global $wgOut; $wgOut->showPermissionsErrorPage( $this->errors, $this->permission ); - $wgOut->output(); + if ( $action === self::SEND_OUTPUT ) { + $wgOut->output(); + } } } diff --git a/includes/exception/ThrottledError.php b/includes/exception/ThrottledError.php index bec0d904be..cdeb402a73 100644 --- a/includes/exception/ThrottledError.php +++ b/includes/exception/ThrottledError.php @@ -32,9 +32,9 @@ class ThrottledError extends ErrorPageError { ); } - public function report() { + public function report( $action = ErrorPageError::SEND_OUTPUT ) { global $wgOut; $wgOut->setStatusCode( 429 ); - parent::report(); + parent::report( $action ); } } diff --git a/includes/exception/UserNotLoggedIn.php b/includes/exception/UserNotLoggedIn.php index 246c944f2e..ff992b07b7 100644 --- a/includes/exception/UserNotLoggedIn.php +++ b/includes/exception/UserNotLoggedIn.php @@ -75,11 +75,11 @@ class UserNotLoggedIn extends ErrorPageError { * Redirect to Special:Userlogin if the specified message is compatible. Otherwise, * show an error page as usual. */ - public function report() { + public function report( $action = self::SEND_OUTPUT ) { // If an unsupported message is used, don't try redirecting to Special:Userlogin, // since the message may not be compatible. if ( !in_array( $this->msg, LoginHelper::getValidErrorMessages() ) ) { - parent::report(); + parent::report( $action ); return; } @@ -99,6 +99,8 @@ class UserNotLoggedIn extends ErrorPageError { 'warning' => $this->msg, ] ) ); - $output->output(); + if ( $action === self::SEND_OUTPUT ) { + $output->output(); + } } } diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php index ff8f0563fb..f095066806 100644 --- a/includes/filerepo/FileRepo.php +++ b/includes/filerepo/FileRepo.php @@ -838,7 +838,11 @@ class FileRepo { /** * Store a file to a given destination. * - * @param string $srcPath Source file system path, storage path, or virtual URL + * Using FSFile/TempFSFile can improve performance via caching. + * Using TempFSFile can further improve performance by signalling that it is safe + * to touch the source file or write extended attribute metadata to it directly. + * + * @param string|FSFile $srcPath Source file system path, storage path, or virtual URL * @param string $dstZone Destination zone * @param string $dstRel Destination relative path * @param int $flags Bitwise combination of the following flags: @@ -862,6 +866,8 @@ class FileRepo { /** * Store a batch of files * + * @see FileRepo::store() + * * @param array $triplets (src, dest zone, dest rel) triplets as per store() * @param int $flags Bitwise combination of the following flags: * self::OVERWRITE Overwrite an existing destination file instead of failing @@ -884,11 +890,18 @@ class FileRepo { $operations = []; // Validate each triplet and get the store operation... foreach ( $triplets as $triplet ) { - list( $srcPath, $dstZone, $dstRel ) = $triplet; + list( $src, $dstZone, $dstRel ) = $triplet; + $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src; wfDebug( __METHOD__ . "( \$src='$srcPath', \$dstZone='$dstZone', \$dstRel='$dstRel' )\n" ); - + // Resolve source path + if ( $src instanceof FSFile ) { + $op = 'store'; + } else { + $src = $this->resolveToStoragePathIfVirtual( $src ); + $op = FileBackend::isStoragePath( $src ) ? 'copy' : 'store'; + } // Resolve destination path $root = $this->getZonePath( $dstZone ); if ( !$root ) { @@ -904,13 +917,10 @@ class FileRepo { return $this->newFatal( 'directorycreateerror', $dstDir ); } - // Resolve source to a storage path if virtual - $srcPath = $this->resolveToStoragePathIfVirtual( $srcPath ); - // Copy the source file to the destination $operations[] = [ - 'op' => FileBackend::isStoragePath( $srcPath ) ? 'copy' : 'store', - 'src' => $srcPath, // storage path (copy) or local file path (store) + 'op' => $op, + 'src' => $src, // storage path (copy) or local file path (store) 'dst' => $dstPath, 'overwrite' => ( $flags & self::OVERWRITE ) ? true : false, 'overwriteSame' => ( $flags & self::OVERWRITE_SAME ) ? true : false, @@ -970,6 +980,10 @@ class FileRepo { * This function can be used to write to otherwise read-only foreign repos. * This is intended for copying generated thumbnails into the repo. * + * Using FSFile/TempFSFile can improve performance via caching. + * Using TempFSFile can further improve performance by signalling that it is safe + * to touch the source file or write extended attribute metadata to it directly. + * * @param string|FSFile $src Source file system path, storage path, or virtual URL * @param string $dst Virtual URL or storage path * @param array|string|null $options An array consisting of a key named headers @@ -981,39 +995,14 @@ class FileRepo { return $this->quickImportBatch( [ [ $src, $dst, $options ] ] ); } - /** - * Purge a file from the repo. This does no locking nor journaling. - * This function can be used to write to otherwise read-only foreign repos. - * This is intended for purging thumbnails. - * - * @param string $path Virtual URL or storage path - * @return Status - */ - final public function quickPurge( $path ) { - return $this->quickPurgeBatch( [ $path ] ); - } - - /** - * Deletes a directory if empty. - * This function can be used to write to otherwise read-only foreign repos. - * - * @param string $dir Virtual URL (or storage path) of directory to clean - * @return Status - */ - public function quickCleanDir( $dir ) { - $status = $this->newGood(); - $status->merge( $this->backend->clean( - [ 'dir' => $this->resolveToStoragePathIfVirtual( $dir ) ] ) ); - - return $status; - } - /** * Import a batch of files from the local file system into the repo. * This does no locking nor journaling and overrides existing files. * This function can be used to write to otherwise read-only foreign repos. * This is intended for copying generated thumbnails into the repo. * + * @see FileRepo::quickImport() + * * All path parameters may be a file system path, storage path, or virtual URL. * When "headers" are given they are used as HTTP headers if supported. * @@ -1046,7 +1035,7 @@ class FileRepo { $operations[] = [ 'op' => $op, - 'src' => $src, + 'src' => $src, // storage path (copy) or local path/FSFile (store) 'dst' => $dst, 'headers' => $headers ]; @@ -1057,6 +1046,33 @@ class FileRepo { return $status; } + /** + * Purge a file from the repo. This does no locking nor journaling. + * This function can be used to write to otherwise read-only foreign repos. + * This is intended for purging thumbnails. + * + * @param string $path Virtual URL or storage path + * @return Status + */ + final public function quickPurge( $path ) { + return $this->quickPurgeBatch( [ $path ] ); + } + + /** + * Deletes a directory if empty. + * This function can be used to write to otherwise read-only foreign repos. + * + * @param string $dir Virtual URL (or storage path) of directory to clean + * @return Status + */ + public function quickCleanDir( $dir ) { + $status = $this->newGood(); + $status->merge( $this->backend->clean( + [ 'dir' => $this->resolveToStoragePathIfVirtual( $dir ) ] ) ); + + return $status; + } + /** * Purge a batch of files from the repo. * This function can be used to write to otherwise read-only foreign repos. @@ -1169,6 +1185,10 @@ class FileRepo { * Returns a Status object. On success, the value contains "new" or * "archived", to indicate whether the file was new with that name. * + * Using FSFile/TempFSFile can improve performance via caching. + * Using TempFSFile can further improve performance by signalling that it is safe + * to touch the source file or write extended attribute metadata to it directly. + * * Options to $options include: * - headers : name/value map of HTTP headers to use in response to GET/HEAD requests * @@ -1199,6 +1219,8 @@ class FileRepo { /** * Publish a batch of files * + * @see FileRepo::publish() + * * @param array $ntuples (source, dest, archive) triplets or * (source, dest, archive, options) 4-tuples as per publish(). * @param int $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate @@ -1277,7 +1299,7 @@ class FileRepo { } else { $operations[] = [ 'op' => 'store', - 'src' => $src, // FSFile (preferred) or local file path + 'src' => $src, // storage path (copy) or local path/FSFile (store) 'dst' => $dstPath, 'overwrite' => true, // replace current 'headers' => $headers diff --git a/includes/filerepo/file/LocalFileLockError.php b/includes/filerepo/file/LocalFileLockError.php index 7cfc8c22c8..b76f3da2a0 100644 --- a/includes/filerepo/file/LocalFileLockError.php +++ b/includes/filerepo/file/LocalFileLockError.php @@ -29,9 +29,9 @@ class LocalFileLockError extends ErrorPageError { ); } - public function report() { + public function report( $action = self::SEND_OUTPUT ) { global $wgOut; $wgOut->setStatusCode( 429 ); - parent::report(); + parent::report( $action ); } } diff --git a/includes/installer/MysqlUpdater.php b/includes/installer/MysqlUpdater.php index ea88411ec6..7d41d04616 100644 --- a/includes/installer/MysqlUpdater.php +++ b/includes/installer/MysqlUpdater.php @@ -386,6 +386,9 @@ class MysqlUpdater extends DatabaseUpdater { [ 'modifyTable', 'job', 'patch-job-params-mediumblob.sql' ], // 1.34 + [ 'dropIndex', 'archive', 'ar_usertext_timestamp', + 'patch-drop-archive-ar_usertext_timestamp.sql' ], + [ 'dropIndex', 'archive', 'usertext_timestamp', 'patch-drop-archive-usertext_timestamp.sql' ], [ 'dropField', 'logging', 'log_user', 'patch-drop-user-fields.sql' ], ]; } diff --git a/includes/installer/WebInstaller.php b/includes/installer/WebInstaller.php index 21ad210f5e..d9cd6dea82 100644 --- a/includes/installer/WebInstaller.php +++ b/includes/installer/WebInstaller.php @@ -638,34 +638,40 @@ class WebInstaller extends Installer { /** * Get HTML for an error box with an icon. * + * @deprecated since 1.34 Use Html::errorBox() instead. * @param string $text Wikitext, get this with wfMessage()->plain() * * @return string */ public function getErrorBox( $text ) { + wfDeprecated( __METHOD__, '1.34' ); return $this->getInfoBox( $text, 'critical-32.png', 'config-error-box' ); } /** * Get HTML for a warning box with an icon. * + * @deprecated since 1.34 Use Html::warningBox() instead. * @param string $text Wikitext, get this with wfMessage()->plain() * * @return string */ public function getWarningBox( $text ) { + wfDeprecated( __METHOD__, '1.34' ); return $this->getInfoBox( $text, 'warning-32.png', 'config-warning-box' ); } /** * Get HTML for an information message box with an icon. * + * @deprecated since 1.34. * @param string|HtmlArmor $text Wikitext to be parsed (from Message::plain) or raw HTML. * @param string|bool $icon Icon name, file in mw-config/images. Default: false * @param string|bool $class Additional class name to add to the wrapper div. Default: false. * @return string HTML */ public function getInfoBox( $text, $icon = false, $class = false ) { + wfDeprecated( __METHOD__, '1.34' ); $html = ( $text instanceof HtmlArmor ) ? HtmlArmor::getHtml( $text ) : $this->parse( $text, true ); diff --git a/includes/libs/rdbms/database/DatabaseMysqlBase.php b/includes/libs/rdbms/database/DatabaseMysqlBase.php index 7be3b7d99d..464e68c410 100644 --- a/includes/libs/rdbms/database/DatabaseMysqlBase.php +++ b/includes/libs/rdbms/database/DatabaseMysqlBase.php @@ -850,27 +850,44 @@ abstract class DatabaseMysqlBase extends Database { } if ( $this->getLBInfo( 'is static' ) === true ) { + $this->queryLogger->debug( + "Bypassed replication wait; database has a static dataset", + $this->getLogContext( [ 'method' => __METHOD__ ] ) + ); + return 0; // this is a copy of a read-only dataset with no master DB } elseif ( $this->lastKnownReplicaPos && $this->lastKnownReplicaPos->hasReached( $pos ) ) { + $this->queryLogger->debug( + "Bypassed replication wait; replication already known to have reached $pos", + $this->getLogContext( [ 'method' => __METHOD__ ] ) + ); + return 0; // already reached this point for sure } // Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set if ( $pos->getGTIDs() ) { - // Ignore GTIDs from domains exclusive to the master DB (presumably inactive) - $rpos = $this->getReplicaPos(); - $gtidsWait = $rpos ? MySQLMasterPos::getCommonDomainGTIDs( $pos, $rpos ) : []; + // Get the GTIDs from this replica server too see the domains (channels) + $refPos = $this->getReplicaPos(); + if ( !$refPos ) { + $this->queryLogger->error( + "Could not get replication position", + $this->getLogContext( [ 'method' => __METHOD__ ] ) + ); + + return -1; // this is the master itself? + } + // GTIDs with domains (channels) that are active and are present on the replica + $gtidsWait = $pos::getRelevantActiveGTIDs( $pos, $refPos ); if ( !$gtidsWait ) { $this->queryLogger->error( - "No GTIDs with the same domain between master ($pos) and replica ($rpos)", - $this->getLogContext( [ - 'method' => __METHOD__, - ] ) + "No active GTIDs in $pos share a domain with those in $refPos", + $this->getLogContext( [ 'method' => __METHOD__, 'activeDomain' => $pos ] ) ); return -1; // $pos is from the wrong cluster? } - // Wait on the GTID set (MariaDB only) + // Wait on the GTID set $gtidArg = $this->addQuotes( implode( ',', $gtidsWait ) ); if ( strpos( $gtidArg, ':' ) !== false ) { // MySQL GTIDs, e.g "source_id:transaction_id" @@ -886,28 +903,28 @@ abstract class DatabaseMysqlBase extends Database { $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)"; } - list( $res, $err ) = $this->executeQuery( $sql, __METHOD__, self::QUERY_IGNORE_DBO_TRX ); - $row = $res ? $this->fetchRow( $res ) : false; - if ( !$row ) { - throw new DBExpectedError( $this, "Replication wait failed: {$err}" ); - } + $res = $this->query( $sql, __METHOD__, self::QUERY_IGNORE_DBO_TRX ); + $row = $this->fetchRow( $res ); // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual $status = ( $row[0] !== null ) ? intval( $row[0] ) : null; if ( $status === null ) { - if ( !$pos->getGTIDs() ) { - // T126436: jobs programmed to wait on master positions might be referencing - // binlogs with an old master hostname; this makes MASTER_POS_WAIT() return null. - // Try to detect this case and treat the replica DB as having reached the given - // position (any master switchover already requires that the new master be caught - // up before the switch). - $replicationPos = $this->getReplicaPos(); - if ( $replicationPos && !$replicationPos->channelsMatch( $pos ) ) { - $this->lastKnownReplicaPos = $replicationPos; - $status = 0; - } - } + $this->queryLogger->error( + "An error occurred while waiting for replication to reach $pos", + $this->getLogContext( [ 'method' => __METHOD__, 'sql' => $sql ] ) + ); + } elseif ( $status < 0 ) { + $this->queryLogger->error( + "Timed out waiting for replication to reach $pos", + $this->getLogContext( [ + 'method' => __METHOD__, 'sql' => $sql, 'timeout' => $timeout + ] ) + ); } elseif ( $status >= 0 ) { + $this->queryLogger->debug( + "Replication has reached $pos", + $this->getLogContext( [ 'method' => __METHOD__ ] ) + ); // Remember that this position was reached to save queries next time $this->lastKnownReplicaPos = $pos; } diff --git a/includes/libs/rdbms/database/position/MySQLMasterPos.php b/includes/libs/rdbms/database/position/MySQLMasterPos.php index fa2c1dbec2..e1b8f9a252 100644 --- a/includes/libs/rdbms/database/position/MySQLMasterPos.php +++ b/includes/libs/rdbms/database/position/MySQLMasterPos.php @@ -190,39 +190,69 @@ class MySQLMasterPos implements DBMasterPos { } /** + * Set the GTID domain known to be used in new commits on a replication stream of interest + * + * This makes getRelevantActiveGTIDs() filter out GTIDs from other domains + * + * @see MySQLMasterPos::getRelevantActiveGTIDs() + * @see https://mariadb.com/kb/en/library/gtid/#gtid_domain_id + * * @param int|null $id @@gtid_domain_id of the active replication stream + * @return MySQLMasterPos This instance (since 1.34) * @since 1.31 */ public function setActiveDomain( $id ) { $this->activeDomain = (int)$id; + + return $this; } /** + * Set the server ID known to be used in new commits on a replication stream of interest + * + * This makes getRelevantActiveGTIDs() filter out GTIDs from other origin servers + * + * @see MySQLMasterPos::getRelevantActiveGTIDs() + * * @param int|null $id @@server_id of the server were writes originate + * @return MySQLMasterPos This instance (since 1.34) * @since 1.31 */ public function setActiveOriginServerId( $id ) { $this->activeServerId = (int)$id; + + return $this; } /** + * Set the server UUID known to be used in new commits on a replication stream of interest + * + * This makes getRelevantActiveGTIDs() filter out GTIDs from other origin servers + * + * @see MySQLMasterPos::getRelevantActiveGTIDs() + * * @param string|null $id @@server_uuid of the server were writes originate + * @return MySQLMasterPos This instance (since 1.34) * @since 1.31 */ public function setActiveOriginServerUUID( $id ) { $this->activeServerUUID = $id; + + return $this; } /** * @param MySQLMasterPos $pos * @param MySQLMasterPos $refPos - * @return string[] List of GTIDs from $pos that have domains in $refPos - * @since 1.31 + * @return string[] List of active GTIDs from $pos that have domains in $refPos + * @since 1.34 */ - public static function getCommonDomainGTIDs( MySQLMasterPos $pos, MySQLMasterPos $refPos ) { - return array_values( - array_intersect_key( $pos->gtids, $refPos->getActiveGtidCoordinates() ) - ); + public static function getRelevantActiveGTIDs( MySQLMasterPos $pos, MySQLMasterPos $refPos ) { + return array_values( array_intersect_key( + $pos->gtids, + $pos->getActiveGtidCoordinates(), + $refPos->gtids + ) ); } /** diff --git a/includes/page/Article.php b/includes/page/Article.php index 4b37181c24..b6e366e953 100644 --- a/includes/page/Article.php +++ b/includes/page/Article.php @@ -1668,7 +1668,7 @@ class Article implements Page { } // the outer div is need for styling the revision info and nav in MobileFrontend - $outputPage->addSubtitle( "
" . $revisionInfo . + $outputPage->addSubtitle( "
" . $revisionInfo . "
" . $cdel . $context->msg( 'revision-nav' )->rawParams( $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index ec1628f8d0..9ec58346cc 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -910,7 +910,7 @@ class Parser { */ public function setTitle( $t ) { if ( !$t ) { - $t = Title::newFromText( 'NO TITLE' ); + $t = Title::makeTitle( NS_SPECIAL, 'Badtitle/Parser' ); } if ( $t->hasFragment() ) { diff --git a/includes/specialpage/QueryPage.php b/includes/specialpage/QueryPage.php index b7eb3c0764..6ed5e125f0 100644 --- a/includes/specialpage/QueryPage.php +++ b/includes/specialpage/QueryPage.php @@ -118,6 +118,30 @@ abstract class QueryPage extends SpecialPage { return $qp; } + /** + * Get a list of query pages disabled and with it's run mode + * @param Config $config + * @return string[] + */ + public static function getDisabledQueryPages( Config $config ) { + $disableQueryPageUpdate = $config->get( 'DisableQueryPageUpdate' ); + + if ( !is_array( $disableQueryPageUpdate ) ) { + return []; + } + + $pages = []; + foreach ( $disableQueryPageUpdate as $name => $runMode ) { + if ( is_int( $name ) ) { + // The run mode may be omitted + $pages[$runMode] = 'disabled'; + } else { + $pages[$name] = $runMode; + } + } + return $pages; + } + /** * A mutator for $this->listoutput; * @@ -632,13 +656,21 @@ abstract class QueryPage extends SpecialPage { # If updates on this page have been disabled, let the user know # that the data set won't be refreshed for now - if ( is_array( $this->getConfig()->get( 'DisableQueryPageUpdate' ) ) - && in_array( $this->getName(), $this->getConfig()->get( 'DisableQueryPageUpdate' ) ) - ) { - $out->wrapWikiMsg( - "
\n$1\n
", - 'querypage-no-updates' - ); + $disabledQueryPages = self::getDisabledQueryPages( $this->getConfig() ); + if ( isset( $disabledQueryPages[$this->getName()] ) ) { + $runMode = $disabledQueryPages[$this->getName()]; + if ( $runMode === 'disabled' ) { + $out->wrapWikiMsg( + "
\n$1\n
", + 'querypage-no-updates' + ); + } else { + // Messages used here: querypage-updates-periodical + $out->wrapWikiMsg( + "
\n$1\n
", + 'querypage-updates-' . $runMode + ); + } } } } diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php index 3b3f2e9a6f..8f92cd5f9d 100644 --- a/includes/specials/SpecialContributions.php +++ b/includes/specials/SpecialContributions.php @@ -42,11 +42,16 @@ class SpecialContributions extends IncludableSpecialPage { $out = $this->getOutput(); // Modules required for viewing the list of contributions (also when included on other pages) $out->addModuleStyles( [ + 'jquery.makeCollapsible.styles', 'mediawiki.interface.helpers.styles', 'mediawiki.special', 'mediawiki.special.changeslist', ] ); - $out->addModules( 'mediawiki.special.recentchanges' ); + $out->addModules( [ + 'mediawiki.special.recentchanges', + // Certain skins e.g. Minerva might have disabled this module. + 'mediawiki.page.ready' + ] ); $this->addHelpLink( 'Help:User contributions' ); $this->opts = []; @@ -58,7 +63,7 @@ class SpecialContributions extends IncludableSpecialPage { if ( !strlen( $target ) ) { if ( !$this->including() ) { - $out->addHTML( $this->getForm() ); + $out->addHTML( $this->getForm( $this->opts ) ); } return; @@ -76,7 +81,7 @@ class SpecialContributions extends IncludableSpecialPage { if ( ExternalUserNames::isExternal( $target ) ) { $userObj = User::newFromName( $target, false ); if ( !$userObj ) { - $out->addHTML( $this->getForm() ); + $out->addHTML( $this->getForm( $this->opts ) ); return; } @@ -88,12 +93,12 @@ class SpecialContributions extends IncludableSpecialPage { } else { $nt = Title::makeTitleSafe( NS_USER, $target ); if ( !$nt ) { - $out->addHTML( $this->getForm() ); + $out->addHTML( $this->getForm( $this->opts ) ); return; } $userObj = User::newFromName( $nt->getText(), false ); if ( !$userObj ) { - $out->addHTML( $this->getForm() ); + $out->addHTML( $this->getForm( $this->opts ) ); return; } $id = $userObj->getId(); @@ -157,7 +162,7 @@ class SpecialContributions extends IncludableSpecialPage { "
\n\$1\n
", [ 'negative-namespace-not-supported' ] ); - $out->addHTML( $this->getForm() ); + $out->addHTML( $this->getForm( $this->opts ) ); return; } @@ -209,9 +214,6 @@ class SpecialContributions extends IncludableSpecialPage { $this->addFeedLinks( $feedParams ); if ( Hooks::run( 'SpecialContributionsBeforeMainOutput', [ $id, $userObj, $this ] ) ) { - if ( !$this->including() ) { - $out->addHTML( $this->getForm() ); - } $pager = new ContribsPager( $this->getContext(), [ 'target' => $target, 'namespace' => $this->opts['namespace'], @@ -225,6 +227,9 @@ class SpecialContributions extends IncludableSpecialPage { 'nsInvert' => $this->opts['nsInvert'], 'associated' => $this->opts['associated'], ], $this->getLinkRenderer() ); + if ( !$this->including() ) { + $out->addHTML( $this->getForm( $this->opts ) ); + } if ( IP::isValidRange( $target ) && !$pager->isQueryableRange( $target ) ) { // Valid range, but outside CIDR limit. @@ -466,9 +471,11 @@ class SpecialContributions extends IncludableSpecialPage { /** * Generates the namespace selector form with hidden attributes. + * @param array $pagerOptions with keys contribs, user, deletedOnly, limit, target, topOnly, + * newOnly, hideMinor, namespace, associated, nsInvert, tagfilter, year, start, end * @return string HTML fragment */ - protected function getForm() { + protected function getForm( array $pagerOptions ) { $this->opts['title'] = $this->getPageTitle()->getPrefixedText(); // Modules required only for the form $this->getOutput()->addModules( [ @@ -645,6 +652,14 @@ class SpecialContributions extends IncludableSpecialPage { $htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() ); $htmlForm ->setMethod( 'get' ) + // When offset is defined, the user is paging through results + // so we hide the form by default to allow users to focus on browsing + // rather than defining search parameters + ->setCollapsibleOptions( + ( $pagerOptions['target'] ?? null ) || + ( $pagerOptions['start'] ?? null ) || + ( $pagerOptions['end'] ?? null ) + ) ->setAction( wfScript() ) ->setSubmitText( $this->msg( 'sp-contributions-submit' )->text() ) ->setWrapperLegend( $this->msg( 'sp-contributions-search' )->text() ); diff --git a/languages/i18n/ban.json b/languages/i18n/ban.json index e834be3519..d4ab79ec9d 100644 --- a/languages/i18n/ban.json +++ b/languages/i18n/ban.json @@ -27,11 +27,11 @@ "tog-numberheadings": "isinin nomor murda anggen cara otomatis", "tog-editondblclick": "sunting lembar nganggen klik kaping pindo", "tog-editsectiononrightclick": "sayagayang panyuntingan kepahan anggen ngeklik tengen ring kepahan judul", - "tog-watchcreations": "imbuhin lembar sane karyanin titiang ring kepahan pangiwasan", + "tog-watchcreations": "Tambeh kaca sané kardi titiang miwah berkas sané unggah titiang ring pangawasan titiang", "tog-watchdefault": "imbuhin lembar panyuntingansane sunting titiang ring kepahan pangiwasan", - "tog-watchmoves": "imbuhang lembar sane kakisidang titiang ring kepahan pangiwasan", + "tog-watchmoves": "Tambeh kaca miwan berkas sané gingsirang titiang ring pangawasan titiang", "tog-watchdeletion": "imbuhin lembar sane kaapus ring kepahan pangiwasan", - "tog-watchuploads": "Tambehin berkas anyar sané unggah tiang ka pupulan pantaun\nTambahkan berkas baru yang saya unggah ke daftar pantauan", + "tog-watchuploads": "Tambeh berkas anyar sané unggah titiang ring pangawasan titiang", "tog-watchrollback": "Tambehin kaca sané sampun uliang tiang ka tengah pupulan pantauan tiangé", "tog-minordefault": "pingetin samian suntingan dados suntingan alit sane ajeg", "tog-previewontop": "tampilang pratayang sadurung kotak sunting lan nenten sadurungnyane", diff --git a/languages/i18n/bg.json b/languages/i18n/bg.json index a209419084..8da3832d0a 100644 --- a/languages/i18n/bg.json +++ b/languages/i18n/bg.json @@ -1710,6 +1710,7 @@ "listfiles-userdoesnotexist": "Няма регистрирана потребителска сметка за „$1“.", "imgfile": "файл", "listfiles": "Списък на файловете", + "listfiles_subpage": "Качвания от $1", "listfiles_thumb": "Миникартинка", "listfiles_date": "Дата", "listfiles_name": "Име на файла", @@ -2426,6 +2427,7 @@ "blocklist-userblocks": "Скриване блокирането на потребителски сметки", "blocklist-tempblocks": "Скриване на временни блокирания", "blocklist-addressblocks": "Скриване на отделни блокирания на IP адреси", + "blocklist-type": "Вид:", "blocklist-type-opt-all": "Всички", "blocklist-type-opt-sitewide": "За всички уикита", "blocklist-type-opt-partial": "Частично", @@ -3037,6 +3039,7 @@ "version-ext-colheader-description": "Описание", "version-ext-colheader-credits": "Автори", "version-license-title": "Лиценз за $1", + "version-credits-title": "Списък на авторите на $1", "version-poweredby-credits": "Това уики работи на базата на [https://www.mediawiki.org/ MediaWiki], copyright © 2001-$1 $2.", "version-poweredby-others": "други", "version-poweredby-translators": "преводачи в translatewiki.net", @@ -3320,6 +3323,7 @@ "feedback-bugornote": "Ако сте готови подробно да опишете технически проблем, моля [$1 докладвайте го тук].\nВ противен случай, можете да използвате лесния формуляр по-долу. Коментарът ви ще бъде добавен към страницата „[$3 $2]“, наред с вашето потребителско име.", "feedback-cancel": "Отказ", "feedback-close": "Готово", + "feedback-external-bug-report-button": "Изпращане на техническа задача", "feedback-dialog-title": "Изпращане на обратна връзка", "feedback-error1": "Грешка: Неразпознат резултат от API", "feedback-error2": "Грешка: Неуспешна редакция", @@ -3348,9 +3352,13 @@ "duration-centuries": "$1 {{PLURAL:$1|век|века}}", "duration-millennia": "$1 {{PLURAL:$1|хилядолетие|хилядолетия}}", "rotate-comment": "Изображението е завъртяно на $1 {{PLURAL:$1|градус|градуса}} по часовниковата стрелка", + "limitreport-cputime": "Употреба на процесорното време", "limitreport-cputime-value": "$1 {{PLURAL:$1|секунда|секунди}}", + "limitreport-walltime": "Употреба в режим на реално време", "limitreport-walltime-value": "$1 {{PLURAL:$1|секунда|секунди}}", + "limitreport-ppvisitednodes": "Брой възли посетени от препроцесора", "limitreport-ppvisitednodes-value": "$1/$2", + "limitreport-ppgeneratednodes": "Брой възли генерирани от препроцесора", "limitreport-ppgeneratednodes-value": "$1/$2", "limitreport-postexpandincludesize-value": "$1/$2 {{PLURAL:$2|байт|байта}}", "limitreport-templateargumentsize-value": "$1/$2 {{PLURAL:$2|байт|байта}}", @@ -3417,6 +3425,7 @@ "special-characters-group-ipa": "IPA", "special-characters-group-symbols": "Символи", "special-characters-group-greek": "Гръцки", + "special-characters-group-greekextended": "Гръцки (разширен)", "special-characters-group-cyrillic": "Кирилица", "special-characters-group-arabic": "Арабски", "special-characters-group-arabicextended": "Разширен арабски", diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 7944a374f8..8092bf6b3d 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -353,6 +353,7 @@ "perfcached": "The following data is cached and may not be up to date. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.", "perfcachedts": "The following data is cached, and was last updated $1. A maximum of {{PLURAL:$4|one result is|$4 results are}} available in the cache.", "querypage-no-updates": "Updates for this page are currently disabled.\nData here will not presently be refreshed.", + "querypage-updates-periodical": "Updates for this page are run periodically.", "viewsource": "View source", "viewsource-title": "View source for $1", "actionthrottled": "Action throttled", diff --git a/languages/i18n/exif/nap.json b/languages/i18n/exif/nap.json index 76bd5e051b..3326a751ad 100644 --- a/languages/i18n/exif/nap.json +++ b/languages/i18n/exif/nap.json @@ -3,7 +3,8 @@ "authors": [ "C.R.", "E. abu Filumena", - "SabineCretella" + "SabineCretella", + "Sannita" ] }, "exif-imagewidth": "Larghezza", @@ -31,10 +32,10 @@ "exif-imagedescription": "Titulo 'e l'immaggene", "exif-make": "Frabbeca ca muntaje 'a camera", "exif-model": "Mudello d' 'a camera", - "exif-software": "Software ausàto", + "exif-software": "Software ausato", "exif-artist": "Autore", "exif-copyright": "Titolare d' 'o Copyright", - "exif-exifversion": "Verzione d'Exif", + "exif-exifversion": "Verzione 'e ll'exif", "exif-flashpixversion": "Verziona Flashpix suppurtata", "exif-colorspace": "Spazio d' 'e culore", "exif-componentsconfiguration": "Significato d'ogne componente", diff --git a/languages/i18n/exif/nds-nl.json b/languages/i18n/exif/nds-nl.json index 484ad328b4..57fac843a1 100644 --- a/languages/i18n/exif/nds-nl.json +++ b/languages/i18n/exif/nds-nl.json @@ -29,7 +29,7 @@ "exif-imagedescription": "Afbealdingname", "exif-make": "Kameramark", "exif-model": "Kameramodel", - "exif-software": "Programmatuur dee bruked wördt", + "exif-software": "Programmatuur dee gebruked wördt", "exif-artist": "Autöör", "exif-copyright": "Autöörsrechtenholder", "exif-exifversion": "Exif-versy", @@ -93,7 +93,7 @@ "exif-gpsaltituderef": "Höygdereferensy", "exif-gpsaltitude": "Höygde", "exif-gpstimestamp": "GPS-tyd (atoomklokke)", - "exif-gpssatellites": "Satelliten dee bruked binnet vöär de meating", + "exif-gpssatellites": "Satelliten dee gebruked binnet vöär de meating", "exif-gpsstatus": "Untvangerståtus", "exif-gpsmeasuremode": "Meatmodus", "exif-gpsdop": "Meatprecisy", @@ -103,7 +103,7 @@ "exif-gpstrack": "Beweagingsrichting", "exif-gpsimgdirectionref": "Referensy vöär afbealdingsrichting", "exif-gpsimgdirection": "Afbealdingsrichting", - "exif-gpsmapdatum": "Geodetiske undersööksgegeavens dee bruked binnet", + "exif-gpsmapdatum": "Geodetiske undersööksgegeavens dee gebruked binnet", "exif-gpsdestlatituderef": "Referensy vöär breyddegråd tot bestemming", "exif-gpsdestlatitude": "Breyddegråd bestemming", "exif-gpsdestlongituderef": "Referensy vöär längdegråd bestemming", @@ -147,7 +147,7 @@ "exif-iimversion": "IIM-versy", "exif-iimcategory": "Kategory", "exif-iimsupplementalcategory": "Anvüllende kategoryen", - "exif-datetimeexpires": "Neet te bruken nå", + "exif-datetimeexpires": "Neet te gebruken nå", "exif-datetimereleased": "Uutbröcht up", "exif-originaltransmissionref": "Oorsprungelike språklokatykode", "exif-identifier": "ID", diff --git a/languages/i18n/fi.json b/languages/i18n/fi.json index 7d8e5964ec..20d2991257 100644 --- a/languages/i18n/fi.json +++ b/languages/i18n/fi.json @@ -3241,7 +3241,7 @@ "version-specialpages": "Toimintosivut", "version-parserhooks": "Jäsenninkytkökset", "version-variables": "Muuttujat", - "version-editors": "Muokkaajat", + "version-editors": "Muokkaimet", "version-antispam": "Roskalinkkien estäminen", "version-other": "Muut", "version-mediahandlers": "Median käsittelijät", diff --git a/languages/i18n/fr.json b/languages/i18n/fr.json index 07f248d7f7..53d54a26c1 100644 --- a/languages/i18n/fr.json +++ b/languages/i18n/fr.json @@ -233,13 +233,13 @@ "thursday": "jeudi", "friday": "vendredi", "saturday": "samedi", - "sun": "Dim.", - "mon": "Lun.", - "tue": "Mar.", - "wed": "Mer.", - "thu": "Jeu.", - "fri": "Ven.", - "sat": "Sam.", + "sun": "dim.", + "mon": "lun.", + "tue": "mar.", + "wed": "mer.", + "thu": "jeu.", + "fri": "ven.", + "sat": "sam.", "january": "janvier", "february": "février", "march": "mars", diff --git a/languages/i18n/gl.json b/languages/i18n/gl.json index 7d18ae6020..3ad54a4c75 100644 --- a/languages/i18n/gl.json +++ b/languages/i18n/gl.json @@ -2843,10 +2843,10 @@ "tooltip-pt-watchlist": "A lista de páxinas cuxas modificacións está a seguir", "tooltip-pt-mycontris": "Lista das súas contribucións", "tooltip-pt-anoncontribs": "Unha lista das modificacións feitas desde este enderezo IP", - "tooltip-pt-login": "Recoméndaselle rexistrarse, se ben non é obrigatorio", + "tooltip-pt-login": "É recomendable que se rexistre, se ben non é obrigatorio", "tooltip-pt-login-private": "Precisa conectarse para usa esta wiki", "tooltip-pt-logout": "Saír ao anonimato", - "tooltip-pt-createaccount": "Recoméndaselle crear unha conta e acceder ao sistema, se ben non é obrigatorio", + "tooltip-pt-createaccount": "É recomendable que cree unha conta e acceda ao sistema, se ben non é obrigatorio", "tooltip-ca-talk": "Conversa acerca do contido desta páxina", "tooltip-ca-edit": "Edite esta páxina", "tooltip-ca-addsection": "Comezar unha nova sección", @@ -2865,7 +2865,7 @@ "tooltip-p-logo": "Visitar a páxina principal", "tooltip-n-mainpage": "Visitar a páxina principal", "tooltip-n-mainpage-description": "Visitar a páxina principal", - "tooltip-n-portal": "Información acerca do proxecto, o que pode facer e os lugares onde atopar as cousas", + "tooltip-n-portal": "Información acerca do proxecto, do que pode facer e dos lugares onde atopar as cousas", "tooltip-n-currentevents": "Información acerca de acontecementos de actualidade", "tooltip-n-recentchanges": "A lista de modificacións recentes no wiki", "tooltip-n-randompage": "Cargar unha páxina ao chou", diff --git a/languages/i18n/it.json b/languages/i18n/it.json index 6a35a73705..50ef0926ea 100644 --- a/languages/i18n/it.json +++ b/languages/i18n/it.json @@ -897,7 +897,7 @@ "undo-norev": "La modifica non può essere annullata perché non esiste o è stata cancellata.", "undo-nochange": "Sembra che la modifica sia già stata annullata.", "undo-summary": "Annullata la modifica $1 di [[Special:Contributions/$2|$2]] ([[User talk:$2|discussione]])", - "undo-summary-anon": "Annulla la modifica $1 di [[Speciale:Contributi/$2|$2]]", + "undo-summary-anon": "Annullata la modifica $1 di [[Special:Contributions/$2|$2]]", "undo-summary-username-hidden": "Annullata la modifica $1 di un utente nascosto", "cantcreateaccount-text": "La registrazione è stata bloccata da [[User:$3|$3]] per questo indirizzo IP ('''$1''').\n\nLa motivazione del blocco fornita da $3 è la seguente: ''$2''", "cantcreateaccount-range-text": "La registrazione da indirizzi IP nell'intervallo $1, che include il tuo ($4), è stata bloccata da [[User:$3|$3]].\n\nLa motivazione fornita da $3 è $2", @@ -1194,6 +1194,7 @@ "prefs-help-email": "L'inserimento del proprio indirizzo email è facoltativo, ma permette di ricevere la propria password qualora venisse dimenticata.", "prefs-help-email-others": "Puoi anche scegliere di lasciare che gli altri ti contattino via posta elettronica con un collegamento dalla tua pagina utente o di discussione.\nIl tuo indirizzo non viene rivelato quando gli altri utenti ti contattano.", "prefs-help-email-required": "L'indirizzo email è obbligatorio.", + "prefs-help-requireemail": "Se selezionato, invieremo solo email per la reimpostazione della password se la persona che sta resettando la password ha fornito sia il nome utente che l'email per questa utenza.", "prefs-info": "Informazioni di base", "prefs-i18n": "Internazionalizzazione", "prefs-signature": "Firma", @@ -2634,6 +2635,7 @@ "ipblocklist-legend": "Cerca un utente bloccato", "blocklist-userblocks": "Nascondi i blocchi degli utenti registrati", "blocklist-tempblocks": "Nascondi i blocchi temporanei", + "blocklist-indefblocks": "Nascondi blocchi infiniti", "blocklist-addressblocks": "Nascondi i blocchi di un solo IP", "blocklist-type": "Tipo:", "blocklist-type-opt-all": "Tutto", @@ -2728,7 +2730,7 @@ "move-page-legend": "Spostamento di pagina", "movepagetext": "Questo modulo consente di rinominare una pagina, spostando tutta la sua cronologia al nuovo nome. La pagina attuale diverrà automaticamente un redirect al nuovo titolo. Puoi aggiornare automaticamente i redirect che puntano al titolo originale. Puoi decidere di non farlo, ma ricordati di verificare che lo spostamento non abbia creato [[Special:DoubleRedirects|doppi redirect]] o [[Special:BrokenRedirects|redirect errati]]. L'onere di garantire che i collegamenti alla pagina restino corretti spetta a chi la sposta.\n\nSi noti che la pagina non sarà spostata se ne esiste già una con il nuovo nome, a meno che quest'ultima non sia costituita solo da un redirect e sia priva di versioni precedenti. In caso di spostamento errato si può quindi tornare subito al vecchio titolo, e non è possibile sovrascrivere per errore una pagina già esistente.\n\nNota:\nUn cambiamento così drastico può creare contrattempi e problemi, soprattutto per le pagine più visitate. Accertarsi di aver valutato le conseguenze dello spostamento prima di procedere.", "movepagetext-noredirectfixer": "Questo modulo consente di rinominare una pagina, spostando tutta la sua cronologia al nuovo nome. La pagina attuale diverrà automaticamente un redirect al nuovo titolo. Controlla che lo spostamento non abbia creato [[Special:DoubleRedirects|doppi redirect]] o [[Special:BrokenRedirects|redirect errati]]. L'onere di garantire che i collegamenti alla pagina restino corretti spetta a chi la sposta.\n\nSi noti che la pagina non sarà spostata se ne esiste già una con il nuovo nome, a meno che non sia costituita solo da un redirect e sia priva di versioni precedenti. In caso di spostamento errato si può quindi tornare subito al vecchio titolo, e non è possibile sovrascrivere per errore una pagina già esistente.\n\nNota:\nUn cambiamento così drastico può creare contrattempi e problemi, soprattutto per le pagine più visitate. Accertarsi di aver valutato le conseguenze dello spostamento prima di procedere.", - "movepagetext-noredirectsupport": "Usare il seguente modulo rinominerà una pagina, spostando tutta la sua cronologia al nuovo nome. È tua la responsabilità di assicurarti che i collegamenti continuino a puntare dove dovrebbero.\n\nNotare che la pagina non sarà spostata se è già presente una pagina presso il nuovo titolo.\nQuesto comporta che in caso di errori puoi spostare nuovamente una pagina al nome precedente, e che non puoi sovrascrivere una pagina esistente.\n\nNota:\nQuesto cambiamento può essere drastico e inaspettato se la pagina è popolare; per favore assicurati di capire le conseguenze di ciò prima di procedere.", + "movepagetext-noredirectsupport": "Questo modulo consente di rinominare una pagina, spostando tutta la sua cronologia al nuovo nome. L'onere di garantire che i collegamenti alla pagina restino corretti spetta a chi la sposta.\n\nSi noti che la pagina non sarà spostata se ne esiste già una con il nuovo nome.\nIn caso di spostamento errato si può quindi tornare subito al vecchio titolo, e non è possibile sovrascrivere per errore una pagina già esistente.\n\nNota:\nUn cambiamento così drastico può creare contrattempi e problemi, soprattutto per le pagine più visitate. Accertarsi di aver valutato le conseguenze dello spostamento prima di procedere.", "movepagetalktext": "Se selezioni questa casella, la corrispondente pagina di discussione sarà spostata automaticamente al nuovo titolo, a meno che esista già una pagina di discussione non vuota.\n\nIn questi casi, se lo ritieni opportuno, dovrai spostare o unire manualmente la pagina.", "moveuserpage-warning": "'''Attenzione:''' Si sta per spostare una pagina utente. Nota che verrà spostata solamente la pagina. L'utente ''non'' sarà rinominato.", "movecategorypage-warning": "Attenzione: si sta per spostare una categoria. Solo questa pagina verrà spostata: tutte le pagine nella vecchia categoria non saranno inserite nella nuova.", diff --git a/languages/i18n/lad.json b/languages/i18n/lad.json index bdfc178923..e04bda1ca4 100644 --- a/languages/i18n/lad.json +++ b/languages/i18n/lad.json @@ -11,7 +11,8 @@ "Universal Life", "לערי ריינהארט", "아라", - "StevenJ81" + "StevenJ81", + "Chabi1" ] }, "tog-underline": "Suliñar los atamientos:", @@ -146,8 +147,8 @@ "mypage": "Hoja", "mytalk": "Diskusyón", "anontalk": "Diskusyón para este adresso de IP", - "navigation": "Navigación", - "and": " y", + "navigation": "Navigasyon", + "and": " i", "faq": "DDS", "actions": "Aksiones", "namespaces": "Espacios de nombres", @@ -158,7 +159,7 @@ "tagline": "De {{SITENAME}}", "help": "Ayudo", "search": "Buxcar", - "searchbutton": "Buxcar", + "searchbutton": "Bushkar", "go": "Ir", "searcharticle": "Yir", "history": "La istoria de la hoja", @@ -892,7 +893,7 @@ "tooltip-pt-logout": "Sal de tu cuento", "tooltip-pt-createaccount": "Te consejamos de avrir un cuento y hazer entrada allá, portanto no sos obligado", "tooltip-ca-talk": "Diskusyón encima del contènido desta hoja", - "tooltip-ca-edit": "Troca esta hoja", + "tooltip-ca-edit": "Troka esta oja", "tooltip-ca-addsection": "Ajusta un kapítolo muevo", "tooltip-ca-viewsource": "Esta hoja está guadrada.\nPuedes ver su manadero", "tooltip-ca-history": "Enderechamientos passados desta hoja", @@ -1058,7 +1059,7 @@ "feedback-cancel": "Anular", "feedback-message": "Messaje", "feedback-subject": "Sujeto", - "searchsuggest-search": "Buxca en {{SITENAME}}", + "searchsuggest-search": "Bushka en {{SITENAME}}", "duration-seconds": "$1{{PLURAL:$1|segundo|segundos}}", "duration-minutes": "$1{{PLURAL:$1|minuto|minutos}}", "duration-hours": "$1{{PLURAL:$1|ora|oras}}", diff --git a/languages/i18n/lv.json b/languages/i18n/lv.json index f355e60f41..8d7916701a 100644 --- a/languages/i18n/lv.json +++ b/languages/i18n/lv.json @@ -467,7 +467,7 @@ "password-login-forbidden": "Šī lietotājvārda un paroles izmantošana ir aizliegta.", "mailmypassword": "Atiestatīt paroli", "passwordremindertitle": "Jauna pagaidu parole no {{SITENAME}}s", - "passwordremindertext": "Kāds (iespējams, Tu pats, no IP adreses $1)\nlūdza, lai nosūtām Tev jaunu {{SITENAME}} ($4) paroli.\nLietotajam $2 pagaidu parole tagad ir $3.\nLudzu, nomaini paroli, kad esi veiksmīgi iekļuvis iekšš.\nTavas pagaidu paroles derīguma termiņš beigsies pēc {{PLURAL:$5|$5 dienām|$5 dienas|$5 dienām}}.\n\nJa paroles pieprasījumu bija nosūtījis kāds cits, vai arī tu atcerējies savu veco paroli, šo var ignorēt. Vecā parole joprojām darbojas.", + "passwordremindertext": "Kāds (no IP adreses $1)\nlūdza, lai nosūtām tev jaunu {{SITENAME}} ($4) paroli.\nLietotajam $2 izveidota pagaidu parole $3. Ja tāds bija tavs mērķis, tagad tev jāpieslēdzas un jānomaina parole. Tavas pagaidu paroles derīguma termiņš beigsies pēc {{PLURAL:$5|$5 dienām|$5 dienas|$5 dienām}}.\n\nJa paroles pieprasījumu bija nosūtījis kāds cits, vai arī tu atcerējies savu veco paroli, šo var ignorēt. Vecā parole joprojām darbojas.", "noemail": "Lietotājs \"$1\" nav reģistrējis e-pasta adresi.", "noemailcreate": "Tev jānorāda derīgu e-pasta adresi", "passwordsent": "Esam nosūtījuši jaunu paroli uz e-pasta adresi, kuru ir norādījis lietotājs $1. Lūdzu, nāc iekšā ar jauno paroli, kad būsi to saņēmis.", diff --git a/languages/i18n/min.json b/languages/i18n/min.json index 1dd2e86d63..b78c96c929 100644 --- a/languages/i18n/min.json +++ b/languages/i18n/min.json @@ -18,7 +18,8 @@ "Zakiy", "Vlad5250", "S Kartika", - "NoiX180" + "NoiX180", + "Pitnia Ayu Saputri" ] }, "tog-underline": "Garih bawahi pautan:", @@ -1200,14 +1201,33 @@ "right-unblockself": "Malapehan sakek surang", "right-protect": "Ubah tingkek palinduangan jo suntiang halaman yang dilinduangi baruntun", "right-editprotected": "Manyuntiang halaman yang dilinduangi sabagai \"{{int:protect-level-sysop}}\"", + "right-editcontentmodel": "manyuntiang model konten sabuah laman", "right-editinterface": "Manyuntiang antarmuko pangguno", "right-editusercss": "Manyuntiang berkas CSS pangguno lain", "right-edituserjson": "Manyuntiang berkas JSON pangguno lain", "right-edituserjs": "Manyuntiang berkas JS pangguno lain", + "right-editsitecss": "manyuntiang CSS untuak sadoalah situs", + "right-editsitejson": "suntiang JSON untuak sadoalah situs", + "right-editsitejs": "suntiang JavaScript untuak sadoalah situs", + "right-editmyusercss": "suntiang berkas CSS pangguno Sanak", + "right-editmyuserjson": "suntiang berkas JSON pangguno Sanak", + "right-editmyuserjs": "suntiang berkas JavaScript pangguno Sanak", + "right-editmyuserjsredirect": "suntiang berkas JavaScript Sanak nan marupokan aliahan", + "right-viewmywatchlist": "Caliak daftar pantauan Sanak", + "right-editmywatchlist": "Manyuntiang daftar Sanak surang. Beberapo kegiatan akan tetap akan manambahan halaman tanpa hak iko.", + "right-viewmyprivateinfo": "Maliek data pribadi Sanak surang (misalnyo: alamaik email, namo asli Sanak)", + "right-editmyprivateinfo": "Manyuntiang data pribadi Sanak surang (misalnyo: alamaik surel, namo asli)", + "right-editmyoptions": "manyuntiang preferensi Sanak", + "right-rollback": "mambaliakan sacaro capek suntiangan-suntiangan pangguno tarakhia nan manyuntiang laman tatantu", + "right-markbotedits": "tandoi pambaliakan revisi sabagai suntiangan bot", "right-noratelimit": "Indak dipangaruahi jo pambatehan jumlah suntiangan", "right-import": "Mangimpor laman dari wiki lain", "right-importupload": "Mangimpor laman dari berkas nan dimuek", + "right-patrol": "manandoi suntiangan pangguno lain sabagai terpatroli", "right-autopatrol": "Suntiangan surang sacaro otomatih ditandoi tapantau", + "right-patrolmarks": "caliak panandoan patroli parubahan tabaru", + "right-unwatchedpages": "mancaliak daftar laman nan indak tapantau", + "right-mergehistory": "manggabuangan revisi-revisi tadahulu dari laman ko", "right-userrights": "Manyuntiang sadoalah hak pangguno", "right-userrights-interwiki": "Manyuntiang hak pangguno-pangguno di wiki lain", "right-siteadmin": "Mangunci jo mambukak kunci basis data", @@ -1303,7 +1323,7 @@ "action-editcontentmodel": "manyuntiang model konten sabuah laman", "action-managechangetags": "buek jo matikan tag", "action-applychangetags": "terapkan tag basamoan jo parubahan Sanak", - "action-changetags": "Tambah jo apuih [[Special:Tags|tag]] arbitrari pado tiok-tiok revisi jo entri log", + "action-changetags": "Tambah jo apuih arbitrari pado tiok-tiok revisi jo entri log", "action-deletechangetags": "apuih tag dari basisdata", "action-purge": "apuih singgahan laman ko", "action-apihighlimits": "manggunoan bateh labiah tinggi dalam kueri API", @@ -1342,6 +1362,9 @@ "recentchanges-legend": "Piliahan parubahan baru", "recentchanges-summary": "Caliak parubahan baru di wiki pado laman ko.
\n;Patunjuak:(bedo) parubahan, (sijarah) riwayaik parubahan, '''B''' laman baru, '''b''' suntiangan bot, '''k''' suntiangan ketek, ! parubahan alun dipatroli,
'''(+ ''bita'')''' isi laman batambah, (- ''bita'') isi laman bakurang, (← Ikhtisar otomatih), (→ Suntiangan bagian)", "recentchanges-noresult": "Indak ado parubahan dalam rantang wakatu ko nan sasuai jo kriteria.", + "recentchanges-timeout": "wakatu mancari alah habih. Sanak mungkin ka mancubo parameter pancarian nan lain.", + "recentchanges-network": "Salamo tajadi kasalahan teknis, indak ado hasil nan biso dimuek. Silahkan cubo untuak manyagarkan halaman liak.", + "recentchanges-notargetpage": "Masuakan namo halaman nan di ateh untuak maliek parubahan nan terkait jo halaman itu.", "recentchanges-feed-description": "Tamuan parubahan baru dalam umpan wiki ko", "recentchanges-label-newpage": "Suntiangan ko mambuek laman baru", "recentchanges-label-minor": "Iko suntiangan ketek", @@ -1350,11 +1373,16 @@ "recentchanges-label-plusminus": "Parubahan ukuran laman dalam bita", "recentchanges-legend-heading": "Katarangan:", "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (caliak pulo [[Special:NewPages|daftar laman nan baru]])", + "recentchanges-submit": "Tunjuakan", + "rcfilters-tag-remove": "Hapuih '$1'", + "rcfilters-legend-heading": " daftar singkekan:", "rcfilters-other-review-tools": "Pakakeh paninjauan lainnyo", "rcfilters-group-results-by-page": "Kalompokan hasil manuruik laman", "rcfilters-activefilters": "Panyariang aktip", "rcfilters-activefilters-hide": "Suruakan", "rcfilters-activefilters-show": "Tunjuakan", + "rcfilters-activefilters-hide-tooltip": "Sambunyian wilayah filter aktif", + "rcfilters-activefilters-show-tooltip": "Tunjukan wilayah filter aktif", "rcfilters-advancedfilters": "Panyariang lanjutan", "rcfilters-limit-title": "Hasil untuak ditampilkan", "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|parubahan}}, $2", @@ -1373,16 +1401,30 @@ "rcfilters-savedqueries-unsetdefault": "Hapuih dari baku", "rcfilters-savedqueries-remove": "Hapuih", "rcfilters-savedqueries-new-name-label": "Namo", + "rcfilters-savedqueries-new-name-placeholder": "Jalehan tujuan panyaringan iko", "rcfilters-savedqueries-apply-label": "Buek panyariang", "rcfilters-savedqueries-apply-and-setdefault-label": "Buek panyariang baku", "rcfilters-savedqueries-cancel-label": "Batalan", "rcfilters-savedqueries-add-new-title": "Simpan pangaturan panyariang ko", "rcfilters-savedqueries-already-saved": "Panyariang ko alah tasimpan. Ubah pangaturan Sanak untuak manyimpan panyariang baru.", + "rcfilters-restore-default-filters": "Kambalian filter awal", + "rcfilters-clear-all-filters": "Hapuih sado panyaringan", "rcfilters-show-new-changes": "Tunjuakan parubahan baru dari $1", "rcfilters-search-placeholder": "Panyariang parubahan (gunokan menu atau cari namo panyariang)", "rcfilters-search-placeholder-mobile": "Panyariang", + "rcfilters-invalid-filter": "Panyaringan indak sah", + "rcfilters-empty-filter": "Indak ado filter aktif. Sado alah kontribusi ditampilan.", "rcfilters-filterlist-title": "Panyariang", + "rcfilters-filterlist-whatsthis": "Baa caro karajonyo?", + "rcfilters-filterlist-feedbacklink": "Agiah tau kami apo nan Sanak pikian tantang alaik-alaik filter ko", + "rcfilters-highlightbutton-title": "Caliak hasilnyo", + "rcfilters-highlightmenu-title": "Piliah warno", + "rcfilters-highlightmenu-help": "Piliah warno untuak manyorot atribut iko", "rcfilters-filterlist-noresults": "Indak ado panyariang ditamukan", + "rcfilters-noresults-conflict": "Hasil indak ditamuan karano kriteria pancariannyo batantangan", + "rcfilters-state-message-subset": "Filter iko indak akan bapangaruh karano hasilnyo disaratoan jo {{PLURAL:$2|}} (cubo caliak untuak mambedaannyo):$1", + "rcfilters-state-message-fullcoverage": "Mamiliah sado panyaringan dalam kalompok iko samo jo indak mamiliah apopun, sahinggo panyaringan ko indak mambarikan hasil. Kalompok itu: $1", + "rcfilters-filtergroup-authorship": "Kontribusi panulih", "rcfilters-filter-editsbyself-label": "Suntiangan Sanak", "rcfilters-filter-editsbyself-description": "Kontribusi Sanak", "rcfilters-filter-editsbyother-label": "Suntiangan urang lain", @@ -1437,6 +1479,7 @@ "rcfilters-filter-logactions-label": "Tindakan tacataik", "rcfilters-filter-logactions-description": "Tindakan administratif, pambuatan akun, pangapusan halaman, pangunggahan...", "rcfilters-hideminor-conflicts-typeofchange": "Jinih parubahan tatantu indak dapek ditandoi sabagai \"ketek\", jadi panyariang ko bakonflik jo panyariang Jinih Parubahan iko: $1", + "rcfilters-typeofchange-conflicts-hideminor": "Jenih parubahann ko berkonflik jo \"Suntiangan ketek\". Jenih parubahan tatantu indak dapek ditandoi sabagai \"ketek\".", "rcfilters-filtergroup-lastrevision": "Revisi tabaru", "rcfilters-filter-lastrevision-label": "Revisi tabaru", "rcfilters-filter-lastrevision-description": "Anyo parubahan tabaru pado laman ko.", @@ -1448,8 +1491,13 @@ "rcfilters-exclude-button-on": "Salain nan tapiliah", "rcfilters-view-tags": "Suntiangan ditandoi", "rcfilters-view-namespaces-tooltip": "Sariang hasil manuruik ruangnamo", + "rcfilters-view-tags-tooltip": "Pisahan hasil manggonoan panyuntingan", + "rcfilters-view-return-to-default-tooltip": "Kembali ka manu panyaringan utamo", + "rcfilters-view-tags-help-icon-tooltip": "Palajari labiah lanjuik tantangan suntiangan jo TAG", "rcfilters-liveupdates-button": "Parubahan langsuang", "rcfilters-liveupdates-button-title-on": "Matian parubahan langsuang", + "rcfilters-liveupdates-button-title-off": "Tampilan parubahan baru katiko parubahan tu alah tajadi", + "rcfilters-watchlist-markseen-button": "Tandoi sadonyo parubahan sabagai taliek", "rcnotefrom": "Di bawah iko adolah {{PLURAL:$5|parubahan|babagai parubahan}} sajak $3, $4 (ditampilkan sampai $1 parubahan).", "rclistfrom": "Tunjuakan parubahan baru mulai dari tanggal $3 $2", "rcshowhideminor": "$1 suntiangan ketek", diff --git a/languages/i18n/nap.json b/languages/i18n/nap.json index e4212521ae..14235e151e 100644 --- a/languages/i18n/nap.json +++ b/languages/i18n/nap.json @@ -197,7 +197,7 @@ "protect": "Prutegge", "protect_change": "càgna", "unprotect": "Càgna prutezzione", - "newpage": "Paggena nòva", + "newpage": "Paggena nova", "talkpagelinktext": "Chiàcchiera", "specialpage": "Paggena speciàle", "personaltools": "Strumiente perzonale", @@ -928,7 +928,7 @@ "shown-title": "Fa verè {{PLURAL:$1|nu risultato|$1 risultate}} pe paggena", "viewprevnext": "Vire ($1 {{int:pipe-separator}} $2) ($3).", "searchmenu-exists": "'''Ncopp' 'o sito esiste na paggena c' 'o nomme \"[[:$1]]\"'''\n{{PLURAL:$2|0=|Vedite pure dint'a l'ati risultate 'e cerca.}}", - "searchmenu-new": "'''Crèa 'a paggena \"[[:$1]]\" ncopp'a stu wiki!''' {{PLURAL:$2|0=|Vedite pure 'a paggena truvata c' 'a recerca vuosta|Vedite pure 'e risultate d\"a recerca}}", + "searchmenu-new": "'''Crèa 'a paggena \"[[:$1]]\" ncopp'a sta wiki!''' {{PLURAL:$2|0=|Vedite pure 'a paggena truvata c' 'a recerca vuosta|Vedite pure 'e risultate d' 'a recerca}}", "searchprofile-articles": "Paggene 'e contenute", "searchprofile-images": "Multimedia", "searchprofile-everything": "Tutto", @@ -1173,7 +1173,7 @@ "right-suppressionlog": "Vide 'e riggistre private", "right-block": "Nun fa fa' cagnamienti a ll'at'utente", "right-blockemail": "Nun fa mannà e-mail a n'utente", - "right-hideuser": "Blocca n'utente e fallo sparì 'a 'o pubbreco", + "right-hideuser": "Fremma n'utente e annascunnillo r'o pubbreco", "right-ipblock-exempt": "Ignora 'e blocche 'e l'IP, 'e blocche automatece e li blocche 'e range 'e l'IP", "right-unblockself": "Sblocca se stesso", "right-protect": "Cagna 'e livelle 'e prutezione 'e cagna paggene prutette ricurzivamente", @@ -1207,7 +1207,7 @@ "right-mergehistory": "Aunisce 'a cronologgia d' 'e paggene", "right-userrights": "Cagna 'e deritte 'e ll'utente", "right-userrights-interwiki": "Cagna 'e deritte 'e ll'utente int'a l'ati wiki", - "right-siteadmin": "Blocca e sblocca 'o database", + "right-siteadmin": "Chiure e arape 'o database", "right-override-export-depth": "Esporta 'e paggene azzeccanno 'e paggene cullegate nfin'a na profondità 'e 5", "right-sendemail": "Manna na mail a ll'at'utente", "right-managechangetags": "Crìa e appiccia/stuta 'e [[Special:Tags|tag]]", @@ -1466,7 +1466,7 @@ "uploadlogpage": "Riggistro 'e carreche", "uploadlogpagetext": "Ccà abbascio song'alencate l'urdeme file carrecate.\nCuntrullate 'a [[Special:NewFiles|gallaria d' 'e file nuove]] pe' ve ffà na guardata cchiù visuale 'e tutto.", "filename": "Nomme d' 'o file", - "filedesc": "Énnece", + "filedesc": "Riepilego", "fileuploadsummary": "Dettaglie:", "filereuploadsummary": "Cagnamiente a 'o file:", "filestatus": "Stato d' 'o copyright:", diff --git a/languages/i18n/nds-nl.json b/languages/i18n/nds-nl.json index 893a2f27d6..91f6f1ee28 100644 --- a/languages/i18n/nds-nl.json +++ b/languages/i18n/nds-nl.json @@ -44,7 +44,7 @@ "tog-enotifusertalkpages": "Stüür my een bericht as myn oaverlegsyde wysigd is.", "tog-enotifminoredits": "Stüür my ouk een bericht by kleine bewarkingen van syden en bestanden", "tog-enotifrevealaddr": "Myn e-postadres lÃ¥ten seen in e-postberichten", - "tog-shownumberswatching": "Et antal brukers bekyken dee disse syde volgt", + "tog-shownumberswatching": "Et antal gebrukers bekyken dee disse syde volgt", "tog-oldsig": "BestÃ¥nde handteykening:", "tog-fancysig": "Underteykening seen as wikitekst (sunder automatiske verwysing)", "tog-uselivepreview": "NÃ¥kyksyde lÃ¥ten seen sunder eyrst te herladen", @@ -52,15 +52,15 @@ "tog-watchlisthideown": "Verbarg myn eigen bewarkingen", "tog-watchlisthidebots": "Verbarg botbrukers", "tog-watchlisthideminor": "Verbarg kleine wysigingen in myn volglyste", - "tog-watchlisthideliu": "Bewarkingen van anmeldede brukers up myn volglyste verbargen", - "tog-watchlisthideanons": "Bewarkingen van anonyme brukers up myn volglyste verbargen", + "tog-watchlisthideliu": "Bewarkingen van anmeldede gebrukers up myn volglyste verbargen", + "tog-watchlisthideanons": "Bewarkingen van anonyme gebrukers up myn volglyste verbargen", "tog-watchlisthidepatrolled": "Wysigingen dee markeerd binnet up volglyste verbargen", - "tog-ccmeonemails": "Stüür my kopien van berichten an andere brukers", + "tog-ccmeonemails": "Stüür my kopyen van berichten an andere gebrukers", "tog-diffonly": "Under wysigingen neet de syde-inhold lÃ¥ten seen.", "tog-showhiddencats": "LÃ¥t verbörgen kategoryen seen", "tog-norollbackdiff": "Wysigingen vordlÃ¥ten nÃ¥ et weaderümmedraien", "tog-useeditwarning": "WÃ¥rschüw my as ik een bewarkede syde afsluten wil dee noch neet seakerd is", - "tog-prefershttps": "Altyd een beveiligde verbinding bruken as jy anmelded binnet", + "tog-prefershttps": "Altyd een beveiligde verbinding gebruken as jy anmelded binnet", "underline-always": "Altyd", "underline-never": "Nooit", "underline-default": "Standard in juw formgeaving of webkyker", @@ -153,7 +153,7 @@ "cancel": "Afbreaken", "moredotdotdot": "Meyr...", "morenotlisted": "Disse lyste is möägelik neet kompleet.", - "mypage": "Brukerssyde", + "mypage": "Gebrukerssyde", "mytalk": "Oaverleg", "anontalk": "Oaverleg", "navigation": "Navigaty", @@ -197,7 +197,7 @@ "views": "WeadergÃ¥ven", "toolbox": "Warktügen", "tool-link-userrights": "{{GENDER:$1|Brukersgruppen}} wysigen", - "tool-link-emailuser": "Disse {{GENDER:$1|bruker}} een bericht stüren", + "tool-link-emailuser": "Disse {{GENDER:$1|gebruker}} een bericht stüren", "imagepage": "Bestandssyde bekyken", "mediawikipage": "Berichtsyde bekyken", "templatepage": "Mal bekyken", @@ -239,14 +239,14 @@ "privacypage": "Project:Gegeavensbeleid", "badaccess": "Geen tostemming", "badaccess-group0": "Jy hebbet geen tostemming üm disse akty uut te voren.", - "badaccess-groups": "Disse akty kan allinnig uutvoord wörden döär brukers uut {{PLURAL:$2|de grup|eyn van de gruppen}}: $1.", + "badaccess-groups": "Disse akty kan allinnig uutvoord wörden döär gebrukers uut {{PLURAL:$2|de grup|eyn van de gruppen}}: $1.", "versionrequired": "Versy $1 van MediaWiki is nöydig", - "versionrequiredtext": "Versy $1 van MediaWiki is nöydig üm disse syde te bruken. See [[Special:Version|Versy]].", + "versionrequiredtext": "Versy $1 van MediaWiki is nöydig üm disse syde te gebruken. See [[Special:Version|Versy]].", "ok": "Okee", "retrievedfrom": "Van \"$1\"", "youhavenewmessages": "{{PLURAL:$3|Jy hebbet}} $1 ($2).", - "youhavenewmessagesfromusers": "{{PLURAL:$4|Jy hebbet}} $1 van {{PLURAL:$3|een andere bruker|$3 brukers}} ($2).", - "youhavenewmessagesmanyusers": "Jy hebbet $1 van een bült brukers ($2).", + "youhavenewmessagesfromusers": "{{PLURAL:$4|Jy hebbet}} $1 van {{PLURAL:$3|een andere gebruker|$3 gebrukers}} ($2).", + "youhavenewmessagesmanyusers": "Jy hebbet $1 van een bült gebrukers ($2).", "newmessageslinkplural": "{{PLURAL:$1|een ny bericht|999=nye berichten}}", "newmessagesdifflinkplural": "läste {{PLURAL:$1|wysiging|999=wysigingen}}", "youhavenewmessagesmulti": "Jy hebbet nye berichten up $1", @@ -278,7 +278,7 @@ "sort-descending": "Afloupend sorteren", "sort-ascending": "Uploupend sorteren", "nstab-main": "Syde", - "nstab-user": "Brukerssyde", + "nstab-user": "Gebrukerssyde", "nstab-media": "Mediasyde", "nstab-special": "Speciale syde", "nstab-project": "Projektsyde", @@ -324,7 +324,7 @@ "delete-hook-aborted": "Et vordsmyten wördt in et wyre skopped döär een topassing van MediaWiki.\nDer is wyder geen informaty beskikbÃ¥r.", "no-null-revision": "Kun geen leadige nye versy maken vöär de syde \"$1\"", "badtitle": "Ungeldige name", - "badtitletext": "De name van de upvrÃ¥gde syde is neet geldig, leadig, of der stünd een verkeyrde intersprÃ¥k- of interwikiverwysing in.\nMöägelik binnet der eyn of meyr teykens bruked dee neet in titels tostÃ¥n binnet.", + "badtitletext": "De name van de upvrÃ¥gde syde is neet geldig, leadig, of der stünd een verkeyrde intersprÃ¥k- of interwikiverwysing in.\nMöägelik binnet der eyn of meyr teykens gebruked dee neet in titels tostÃ¥n binnet.", "perfcached": "Disse gegeavens kummen uut et tüskengehöägen en binnet meskeen neet aktueel. Der {{PLURAL:$1|is houguut eyn resultaat|binnet houguut $1 resultaten}} beskikbÃ¥r in et tüskengehöägen.", "perfcachedts": "Disse gegeavens kummen uut et tüskengehöägen dee vöär et lätst bywarked is up $2 üm $3. Der {{PLURAL:$4|is houguut eyn resultaat|binnet houguut $4 resultaten}} beskikbÃ¥r in et tüskengehöägen.", "querypage-no-updates": "Disse syde wördt neet bywarked.\nGegeavens up disse syde wördet neet vervarsd.", @@ -335,13 +335,13 @@ "protectedpagetext": "Disse syde is beveiligd. Bewarken of andere handelingen binnet neet möägelik.", "viewsourcetext": "Jy künnet de brontekst van disse syde bewarken en bekyken.", "viewyourtext": "Jy künnet juw bewarkingen an de brontekst van disse syde bekyken en kopieren.", - "protectedinterface": "Up disse syde steyt tekst dee bruked wördt vöär systeemteksten van disse wiki, en is beveiligd üm misbruuk te vöärkommen. Bruuk [https://translatewiki.net/ translatewiki.net], et lokaliseringsprojekt vöär MediaWiki, üm oaversetingen vöär alle wikis to te vogen of te wysigen.", - "editinginterface": "WÃ¥rsküwing: jy bewarket een syde dee teksten bruukt vöär de brukersümgeaving van de programmatuur. \nWat jy up disse syde wysigen is van invlööd up de brukersümgeaving van andere brukers van disse wiki.", - "translateinterface": "Üm oaversettingen vöär alle wikis to te vogen of te wysigen, kün jy [https://translatewiki.net/ translatewiki.net] bruken, et lokaliseringsprojekt vöär MediaWiki.", + "protectedinterface": "Up disse syde steyt tekst dee gebruked wördt vöär systeemteksten van disse wiki, en is beveiligd üm misbruuk te vöärkommen. Bruuk [https://translatewiki.net/ translatewiki.net], et lokaliseringsprojekt vöär MediaWiki, üm oaversetingen vöär alle wikis to te vogen of te wysigen.", + "editinginterface": "WÃ¥rsküwing: jy bewarket een syde dee teksten gebruukt vöär de gebrukersümgeaving van de programmatuur. \nWat jy up disse syde wysigen is van invlööd up de gebrukersümgeaving van andere gebrukers van disse wiki.", + "translateinterface": "Üm oaversettingen vöär alle wikis to te vogen of te wysigen, kün jy [https://translatewiki.net/ translatewiki.net] gebruken, et lokaliseringsprojekt vöär MediaWiki.", "cascadeprotected": "Disse syde is beveiligd ümdat et vöärkümt in de volgende {{PLURAL:$1|syde|syden}}, dee beveiligd {{PLURAL:$1|is|binnet}} mid de \"kaskade\"-opty:\n$2", "namespaceprotected": "Jy möäget geen syden in de $1-naamruumde bewarken.", - "customcssprotected": "Jy möäget disse CSS-syde neet bewarken, ümdat der persoonlike instellingen van een andere bruker in stÃ¥n.", - "customjsprotected": "Jy möäget disse JavaScript-syde neet bewarken, ümdat der persoonlike instellingen van een andere bruker in stÃ¥n.", + "customcssprotected": "Jy möäget disse CSS-syde neet bewarken, ümdat der persoonlike instellingen van een andere gebruker in stÃ¥n.", + "customjsprotected": "Jy möäget disse JavaScript-syde neet bewarken, ümdat der persoonlike instellingen van een andere gebruker in stÃ¥n.", "mycustomcssprotected": "Jy hebbet geen rechten üm disse CSS-syde te bewarken.", "mycustomjsprotected": "Jy hebbet geen rechten üm disse JavaScript-syde te bewarken.", "myprivateinfoprotected": "Jy hebbet geen rechten üm juw priveegegeavens te bewarken.", @@ -359,10 +359,10 @@ "logouttext": "'''Je bin noen aofemeld.'''\n\nt Kan ween dat der wat ziejen bin die weeregeven wörden as of je an-emeld bin totda'j t tussengeheugen van joew webkieker leegmaken.", "welcomeuser": "Welkom, $1!", "welcomecreation-msg": "Juw gebrukerskonto is anmaked.\nVergeat neet juw [[Special:Preferences|vöärköären vöär {{SITENAME}}]] in te stellen.", - "yourname": "Brukersname", - "userlogin-yourname": "Brukersname", - "userlogin-yourname-ph": "Geav juw brukersname up", - "createacct-another-username-ph": "Geav de brukersname up", + "yourname": "Gebrukersname", + "userlogin-yourname": "Gebrukersname", + "userlogin-yourname-ph": "Geav juw gebrukersname up", + "createacct-another-username-ph": "Geav de gebrukersname up", "yourpassword": "Wachtwoord", "userlogin-yourpassword": "Wachtwoord", "userlogin-yourpassword-ph": "Geav juw wachtwoord up", @@ -371,7 +371,7 @@ "createacct-yourpasswordagain": "Bevästig wachtwoord", "createacct-yourpasswordagain-ph": "Geav et wachtwoord upny up", "userlogin-remembermypassword": "Vanselv anmelden", - "userlogin-signwithsecure": "Beveiligde verbinding bruken", + "userlogin-signwithsecure": "Beveiligde verbinding gebruken", "yourdomainname": "Juw domein", "password-change-forbidden": "Je kunnen joew wachtwoord niet wiezigen op disse wiki.", "externaldberror": "Der gung iets fout bie de externe authentisering, of je maggen je gebrukersprofiel niet bewarken.", @@ -380,7 +380,7 @@ "logout": "Afmelden", "userlogout": "Aofmelden", "notloggedin": "Neet an-emelded", - "userlogin-noaccount": "Heb jy noch geen brukersname?", + "userlogin-noaccount": "Heb jy noch geen gebrukersname?", "userlogin-joinproject": "Wörd lid van {{SITENAME}}", "createaccount": "Inskryven", "userlogin-resetpassword-link": "Wachtwoord vergeaten?", @@ -389,7 +389,7 @@ "userlogin-createanother": "Een andere gebrukerskonto anmaken", "createacct-emailrequired": "Netpostadres", "createacct-emailoptional": "Netpostadres (niet verplicht)", - "createacct-email-ph": "Geef joew netpostadres op", + "createacct-email-ph": "Geav juw netpostadresse up", "createacct-another-email-ph": "Vul joew netpostadres in", "createaccountmail": "Gebruuk n tiejelik wachtwoord dat joe netzelde is en stuur t naor t op-egeven netpostadres", "createacct-realname": "Echte naam (niet verplicht)", @@ -397,10 +397,10 @@ "createacct-reason-ph": "Waorumme je n aandere gebrukerskonto anmaken", "createacct-submit": "Gebrukerskonto anmaken", "createacct-another-submit": "Gebrukerskonto anmaken", - "createacct-benefit-heading": "{{SITENAME}} wörden emaakt deur meensen zo as jie.", - "createacct-benefit-body1": "bewarking{{PLURAL:$1||en}}", - "createacct-benefit-body2": "{{PLURAL:$1|zied|ziejen}}", - "createacct-benefit-body3": "aktieve {{PLURAL:$1|biedrager|biedragers}}", + "createacct-benefit-heading": "{{SITENAME}} is maked döär mensken so as ju.", + "createacct-benefit-body1": "{{PLURAL:$1|bewarking|bewarkingen}}", + "createacct-benefit-body2": "{{PLURAL:$1|syde|syden}}", + "createacct-benefit-body3": "aktive {{PLURAL:$1|bydrager|bydragers}}", "badretype": "De wachtwoorden die'j in-etikt hebben bin niet liek alleens.", "userexists": "Disse gebrukersnaam is al gebruuk.\nKies n aandere naam.", "loginerror": "Anmeldingsfout", @@ -472,7 +472,7 @@ "resetpass-wrong-oldpass": "t Veurlopige wachtwoord of t wachtwoord da'j noen hebben is ongeldig.\nMisschien he'j t wachtwoord al ewiezigd of n niej veurlopig wachtwoord an-evreugen.", "resetpass-temp-password": "Veurlopig wachtwoord:", "resetpass-abort-generic": "De wachtwoordwieziging is aofebreuken deur n uutbreiding.", - "passwordreset": "Wachtwoord opniej instellen", + "passwordreset": "Wachtwoord upny instellen", "passwordreset-text-one": "Vul dit formulier in um joew wachtwoord opniej in te stellen.", "passwordreset-text-many": "{{PLURAL:$1|Vul een van de gegevensvelden in um per netpost n tiejelik wachtwoord te ontvangen.}}", "passwordreset-disabled": "Je kunnen op disse wiki joew wachtwoord niet opniej instellen.", @@ -513,7 +513,7 @@ "showpreview": "Bewarking nÃ¥kyken", "showdiff": "Verskil bekyken", "blankarticle": "Waorschuwing: de zied die'j anmaken willen is leeg.\nA'j noen weer op \"$1\" klikken, dan wördt de zied an-emaakt zonder enige inhoud.", - "anoneditwarning": "WÃ¥rsküwing: jy binnet neet anmelded. Juw IP-adresse sal vöär ydereyne sichtbÃ¥r weasen as jy wysigingen up disse syde anbrenget. As jy juw eigen [$1 anmeldet] of [$2 inskryvet] dan kommen juw bewarkingen under juw brukersname te stÃ¥n, samen mid andere vöärdeylen.", + "anoneditwarning": "WÃ¥rsküwing: jy binnet neet anmelded. Juw IP-adresse sal vöär ydereyne sichtbÃ¥r weasen as jy wysigingen up disse syde anbrenget. As jy juw eigen [$1 anmeldet] of [$2 inskryvet] dan kommen juw bewarkingen under juw gebrukersname te stÃ¥n, samen mid andere vöärdeylen.", "anonpreviewwarning": "''Je bin niet an-emeld.''\n''Deur de bewarking op te slaon wörden joew IP-adres op-esleugen in de ziedgeschiedenisse.''", "missingsummary": "'''Herinnering:''' je hebben gien samenvatting op-egeven veur de bewarking. A'j noen weer op ''Opslaon'' klikken wörden de bewarking zonder samenvatting op-esleugen.", "missingcommenttext": "Skryv een upmarking.", @@ -521,7 +521,7 @@ "summary-preview": "Samenvatting nÃ¥kyken:", "subject-preview": "Underwarp nÃ¥kyken:", "blockedtitle": "Gebruker is eblokkeerd", - "blockedtext": "Juw brukersname of IP-adres is blokkeerd.\n\nJy binnet blokkeerd döär $1.\nDe upgeaven readen is $2.\n\n* Blokkeerd vanaf: $8\n* Blokkeerd tot: $6\n* Bedoold üm te blokkeren: $7\n\nJy künnet kontakt upneamen mid $1 of een andere [[{{MediaWiki:Grouppage-sysop}}|beheyrder]] üm de blokkering te beprÃ¥ten.\nJy künnet de funkty \"{{int:emailuser}}\" neet bruken, behalven as jy een geldig e-postadres in juw [[Special:Preferences|instellingen]] upgeaven hebbet en et gebruuk van disse funkty neet blokkeerd is.\nEt IP-adres wat jy nu bruket is $3, en et blokkeringsnummer is #$5.\nVermeld de gegeavens dee hyrboaven stÃ¥n as jy argens up disse blokkering reageren.", + "blockedtext": "Juw gebrukersname of IP-adres is blokkeerd.\n\nJy binnet blokkeerd döär $1.\nDe upgeaven readen is $2.\n\n* Blokkeerd vanaf: $8\n* Blokkeerd tot: $6\n* Bedoold üm te blokkeren: $7\n\nJy künnet kontakt upneamen mid $1 of een andere [[{{MediaWiki:Grouppage-sysop}}|beheyrder]] üm de blokkering te beprÃ¥ten.\nJy künnet de funkty \"{{int:emailuser}}\" neet gebruken, behalven as jy een geldig e-postadres in juw [[Special:Preferences|instellingen]] upgeaven hebbet en et gebruuk van disse funkty neet blokkeerd is.\nEt IP-adres wat jy nu gebruket is $3, en et blokkeringsnummer is #$5.\nVermeld de gegeavens dee hyrboaven stÃ¥n as jy argens up disse blokkering reageren.", "autoblockedtext": "Joew IP-adres is automaties eblokkeerd umdat t gebruukt wördt deur n aandere gebruker, die eblokkeerd wördt deur $1.\nDe reden hierveur was:\n\n:''$2''\n\n* Begint: $8\n* Löp of nao: $6\n* Wee eblokkeerd wördt: $7\n\nJe kunnen kontakt opnemen mit $1 of n van de aandere\n[[{{MediaWiki:Grouppage-sysop}}|beheerders]] um de blokkering te bepraoten.\n\nNB: je kunnen de opsie \"n bericht sturen\" niet gebruken, behalven a'j n geldig netpostadres op-egeven hebben in de [[Special:Preferences|gebrukersveurkeuren]] en je niet eblokkeerd bin.\n\nJoew IP-adres is $3 en joew blokkeernummer is $5.\nGeef disse nummers deur a'j kontakt mit ene opnemen over de blokkering.", "blockednoreason": "gien reden op-egeven", "whitelistedittext": "Um ziejen te kunnen wiezigen, mu'j $1 ween", @@ -534,8 +534,8 @@ "accmailtitle": "Wachtwoord is verstuurd.", "accmailtext": "Der is n willekeurig wachtwoord veur [[User talk:$1|$1]] verstuurd naor $2. t Kan ewiezigd wörden op de zied ''[[Special:ChangePassword|wachtwoord wiezigen]]'' naoda'j an-emeld bin.", "newarticle": "(Niej)", - "newarticletext": "Disse zied besteet nog niet.\nIn t veld hieronder ku'j wat schrieven um disse zied an te maken (meer informasie vie'j op de [$1 hulpzied]).\nA'j hier per ongelok terechtekeumen bin gebruuk dan de knoppe '''veurige''' um weerumme te gaon.", - "anontalkpagetext": "----\nDisse oaverlegsyde höyrt by een anonyme bruker dee noch geen brukersname hevt, of et neet bruukt.\nDÃ¥rümme bruken wy et IP-adresse ter identifikaty. Een IP-adresse kan döär meyrere lüde bruked wörden. As jy een anonyme bruker binnet, en et gevööl hebbet dat jy berichten kryget dee neet vöär ju bedoold binnet [[Special:CreateAccount|skryv ju eigen dan in]] of [[Special:UserLogin|meld ju eigen an]] üm verwarring mid andere anonyme brukers in de tokumst te vöärkommen.''", + "newarticletext": "Disse syde besteyt noch neet.\nIn et veld hyrunder kün jy wat skryven üm disse syde an te maken (meyr informaty vind jy up de [$1 hülpsyde]).\nAs jy hyr per ungelük terechtekommen binnet bruuk dan de knoppe '''vöärige''' üm terügge te gÃ¥n.", + "anontalkpagetext": "----\nDisse oaverlegsyde höyrt by een anonyme gebruker dee noch geen gebrukersname hevt, of et neet bruukt.\nDÃ¥rümme gebruken wy et IP-adresse ter identifikaty. Een IP-adresse kan döär meyrere lüde gebruked wörden. As jy een anonyme gebruker binnet, en et gevööl hebbet dat jy berichten kryget dee neet vöär ju bedoold binnet [[Special:CreateAccount|skryv ju eigen dan in]] of [[Special:UserLogin|meld ju eigen an]] üm verwarring mid andere anonyme gebrukers in de tokumst te vöärkommen.''", "noarticletext": "Der steyt nun geen tekst up disse syde.\nJy künnet [[Special:Search/{{PAGENAME}}|de titel upsöken]] in andere syden,\n[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} söken in de logboken],\nof [{{fullurl:{{FULLPAGENAME}}|action=edit}} disse syde anmaken].", "noarticletext-nopermission": "Up disse syde steyt geen tekst.\nJy künnet [[Special:Search/{{PAGENAME}}|söken nÃ¥ disse term]] in andere syden of\n[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} de logboken döärsöken], mär jy hebbet geen rechten üm disse syde an te maken.", "missing-revision": "De versie #$1 van de zied \"{{FULLPAGENAME}} besteet niet.\n\nDit kömp meestentieds deur t volgen van n verouwerde verwiezing naor n zied die vortedaon is.\nWaorschienlik ku'j der meer gegevens over vienen in t [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} vortdologboek].", @@ -561,7 +561,7 @@ "edit_form_incomplete": "'''Partie delen van t bewarkingsformulier hebben de server niet bereikt. Kiek eers nao of de bewarkingen kloppen en probeer t opniej.'''", "editing": "Bewarken: $1", "creating": "Anmaken: $1", - "editingsection": "Bewarken: $1 (deelzied)", + "editingsection": "Bewarken: $1 (deylsyde)", "editingcomment": "Bewarken: $1 (niej onderwarp)", "editconflict": "Tegelieke bewörken: $1", "explainconflict": "'''NB:''' n aander hef disse zied ewiezigd naoda'j an disse bewarking begunnen bin.\nt Bovenste bewarkingsveld löt de zied zien zo as t noen is.\nDaoronder (bie \"Wiezigingen\") staon de verschillen tussen joew versie en de op-esleugen zied.\nHelemaole onderan (bie \"Joew tekste\") steet nog n bewarkingsveld mit joew versie.\nJe zullen je eigen wiezigingen in de nieje tekste in mutten passen.\n'''Allinnig''' de tekste in t bovenste veld wörden beweerd a'j noen kiezen veur \"$1\".", @@ -577,12 +577,12 @@ "semiprotectedpagewarning": "'''Let op:''' disse zied is beveiligd en ku'j allinnig bewarken a'j n eregistreerden gebruker bin.\nDe leste logboekregel steet hieronder:", "cascadeprotectedwarning": "'''Waorschuwing:''' disse zied is beveiligd, zodat allinnig beheerders disse zied bewarken kunnen, dit wörden edaon umdat disse zied veurkömp in de volgende {{PLURAL:$1|kaskade-beveiligden zied|kaskade-beveiligden ziejen}}:", "titleprotectedwarning": "'''Waorschuwing: disse zied is beveiligd. Je hebben [[Special:ListGroupRights|bepaolde rechten]] neudig um t an te kunnen maken.'''\nDe leste logboekregel steet hieronder:", - "templatesused": "{{PLURAL:$1|Mal|Mallen}} die op disse zied gebruukt wörden:", + "templatesused": "{{PLURAL:$1|Mal|Mallen}} dee up disse syde gebruked wörden:", "templatesusedpreview": "{{PLURAL:$1|Mal|Mallen}} die in disse bewarking gebruukt wörden:", "templatesusedsection": "{{PLURAL:$1|Mal|Mallen}} die in dit subkopjen gebruukt wörden:", "template-protected": "(beveiligd)", "template-semiprotected": "(halv-beveiligd)", - "hiddencategories": "Disse zied völt in de volgende verbörgen {{PLURAL:$1|kategorie|kategorieën}}:", + "hiddencategories": "Disse syde valt in de volgende verbörgen {{PLURAL:$1|kategory|kategoryen}}:", "edittools": "", "nocreatetext": "Disse webstee hef de meugelikheid um nieje ziejen an te maken beteund. Je kunnen ziejen die al bestaon wiezigen of je kunnen je [[Special:UserLogin|anmelden of n gebrukerszied anmaken]].", "nocreate-loggedin": "Je hebben gien toestemming um nieje ziejen an te maken.", @@ -590,9 +590,9 @@ "sectioneditnotsupported-text": "Je kunnen op disse zied gien seksies bewarken.", "permissionserrors": "Gien toestemming", "permissionserrorstext": "Je maggen of kunnen dit niet doon. De {{PLURAL:$1|reden|redens}} daorveur {{PLURAL:$1|is|bin}}:", - "permissionserrorstext-withaction": "Je hebben gien recht um $2, mit de volgende {{PLURAL:$1|reden|redens}}:", + "permissionserrorstext-withaction": "Jy hebbet geen rechten üm $2, mid de volgende {{PLURAL:$1|readen|readenen}}:", "recreate-moveddeleted-warn": "'''Waorschuwing: je maken n zied an die eerder al vortedaon is.'''\n\nBedenk eerst of t neudig is um disse zied veerder te bewarken.\nVeur de dudelikheid steet hieronder t vortdologboek en t herneumlogboek veur disse zied:", - "moveddeleted-notice": "Disse zied is vortedaon.\nHieronder steet de informasie uut t vortdologboek, t beveiligingslogboek, en t herneumlogboek.", + "moveddeleted-notice": "Disse syde is vorddÃ¥n.\nHyrunder steyt de informaty uut et vortsmytlogbook, et beveiligingslogbook, en et hernöömlogbook.", "log-fulllog": "t Hele logboek bekieken", "edit-hook-aborted": "De bewarking is aofebreuken deur n hook.\nDer is gien reden op-egeven.", "edit-gone-missing": "De zied kon niet bie-ewörken wörden.\nt Lik derop as of t vortedaon is.", @@ -633,15 +633,15 @@ "undo-summary": "Versie $1 van [[Special:Contributions/$2|$2]] ([[User talk:$2|overleg]]) weerummedreid", "undo-summary-username-hidden": "Versie $1 deur n verbörgen gebruker weerummedreid", "cantcreateaccount-text": "t Anmaken van gebrukers van dit IP-adres ($1) is eblokkeerd deur [[User:$3|$3]].\n\nDe deur $3 op-egeven reden is ''$2''", - "viewpagelogs": "Bekiek logboeken veur disse zied", + "viewpagelogs": "Bekyk logboken vöär disse syde", "nohistory": "Der bin gien eerdere versies van disse zied.", "currentrev": "Leste versie", - "currentrev-asof": "Leste versie van $1", + "currentrev-asof": "Lätste versy van $1", "revisionasof": "Versy up $1", - "revision-info": "Versie op $1 van {{GENDER:$6|$2}}$7", + "revision-info": "Versy up $1 van {{GENDER:$6|$2}}$7", "previousrevision": "← eyrere versy", - "nextrevision": "niejere versie →", - "currentrevisionlink": "versie zo as t noen is", + "nextrevision": "nyere versy →", + "currentrevisionlink": "Aktuele versy", "cur": "aktueel", "next": "Volgende", "last": "lätste", @@ -745,8 +745,8 @@ "mergelog": "Samenvoegingslogboek", "revertmerge": "Samenvoeging weerummedreien", "mergelogpagetext": "Hieronder zie'j n lieste van de leste samenvoegingen van n ziedgeschiedenisse naor n aandere.", - "history-title": "Versiegeschiedenisse van \"$1\"", - "difference-title": "Verschil tussen versies van \"$1\"", + "history-title": "Versygeskydenisse van \"$1\"", + "difference-title": "Verskil tüsken versys van \"$1\"", "difference-title-multipage": "Verschil tussen ziejen \"$1\" en \"$2\"", "difference-multipage": "(Verschil tussen ziejen)", "lineno": "Regel $1:", @@ -754,8 +754,8 @@ "showhideselectedversions": "Ekeuzen versies bekieken/verbargen", "editundo": "weaderümmedraien", "diff-empty": "(Gien verschil)", - "diff-multi-sameuser": "({{PLURAL:$1|n Tussenliggende versie|$1 tussenliggende versies}} deur de zelfde gebruker is verbörgen)", - "diff-multi-otherusers": "({{PLURAL:$1|Eyn tüskenliggende versy|$1 tüskenliggende versys}} döär {{PLURAL:$2|eyn andere bruker|$2 brukers}} neet weadergeaven)", + "diff-multi-sameuser": "({{PLURAL:$1|Eyn tüskenliggende versy|$1 tüskenliggende versys}} döär deselvde gebruker is verbörgen)", + "diff-multi-otherusers": "({{PLURAL:$1|Eyn tüskenliggende versy|$1 tüskenliggende versys}} döär {{PLURAL:$2|eyn andere gebruker|$2 gebrukers}} neet weadergeaven)", "diff-multi-manyusers": "($1 tussenliggende {{PLURAL:$1|versie|versies}} deur meer as $2 {{PLURAL:$2|gebruker|gebrukers}} niet weeregeven)", "difference-missing-revision": "{{PLURAL:$2|Eén versie|$2 versies}} van disse verschillen ($1) {{PLURAL:$2|is|bin}} niet evunnen.\n\nDit kömp meestentieds deur t volgen van n verouwerde verwiezing naor n zied die vortedaon is.\nWaorschienlik ku'j der meer gegevens over vienen in t [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} vortdologboek].", "searchresults": "Söökresultaten", @@ -784,7 +784,7 @@ "search-redirect": "(döärverwysing vanaf $1)", "search-section": "(underwarp $1)", "search-file-match": "(kümt oavereyne mid de inhold van et bestand)", - "search-suggest": "Bedoelden je: $1", + "search-suggest": "Bedoolde jy: $1", "search-interwiki-caption": "Resultaten van süsterprojekten", "search-interwiki-default": "Resultaoten van $1:", "search-interwiki-more": "(meer)", @@ -882,7 +882,7 @@ "gender-unknown": "Geslachtsneutrale anspreaksform", "gender-male": "Hee bewarkt syden", "gender-female": "See bewarkt syden", - "prefs-help-gender": "Disse instelling is optioneel.\nDe programmatuur bruukt disse waerde um ju up de jüüste manere an te spreaken en vöär andere brukers üm juw geslacht an te geaven.\nDisse informaty is sichtbÃ¥r vöär andere brukers.", + "prefs-help-gender": "Disse instelling is optioneel.\nDe programmatuur gebruukt disse waerde um ju up de jüüste manere an te spreaken en vöär andere gebrukers üm juw geslacht an te geaven.\nDisse informaty is sichtbÃ¥r vöär andere gebrukers.", "email": "Privéberichten", "prefs-help-realname": "Echte naam is keuzevrie.\nA'j disse opsie invullen zu'w joew echte naam gebruken um erkenning te geven veur joew wark.", "prefs-help-email": "n Netpostadres is niet verplicht, mer zo ku'w wel joew wachtwoord toesturen veur a'j t vergeten bin.", @@ -1009,7 +1009,7 @@ "right-siteadmin": "De databanke blokkeren en weer vriegeven", "right-override-export-depth": "Ziejen exporteren, oek de ziejen waor naor verwezen wördt, tot n diepte van 5", "right-sendemail": "Bericht versturen naor aandere gebrukers", - "newuserlogpage": "Logbook van nye brukers", + "newuserlogpage": "Logbook van nye gebrukers", "newuserlogpagetext": "Hieronder staon de niej in-eschreven gebrukers", "rightslog": "Gebrukersrechtenlogboek", "rightslogtext": "Dit is n logboek mit veraanderingen van gebrukersrechten", @@ -1103,7 +1103,7 @@ "rcfilters-filter-user-experience-level-newcomer-label": "Anwas", "rcfilters-filter-user-experience-level-newcomer-description": "An-emelden bewarkers dee minder as 10 bewarkingen edÃ¥n hebben of 4 dagen aktiv ewesd hebben.", "rcfilters-filter-user-experience-level-learner-label": "Leyrlingen", - "rcfilters-filter-user-experience-level-learner-description": "Anmeldede bewarkers mid meyr ervaring as \"anwas\", mär minder as \"ervaren brukers\".", + "rcfilters-filter-user-experience-level-learner-description": "Anmeldede bewarkers mid meyr ervaring as \"anwas\", mär minder as \"ervaren gebrukers\".", "rcfilters-filter-user-experience-level-experienced-label": "ErvÃ¥ren gebrukers", "rcfilters-filter-user-experience-level-experienced-description": "An-emelde bewarkers mid meyr as 500 bewarkingen en 30 dagen van aktiviteit.", "rcfilters-filter-bots-label": "Bot", @@ -1138,28 +1138,28 @@ "rcfilters-filter-previousrevision-description": "Alle wysigingen dee neet de \"lätste versy\" binnen.", "rcfilters-view-tags": "Emarkeerde wysigingen", "rcfilters-view-namespaces-tooltip": "Filter resultaten up naamruumde", - "rcfilters-view-tags-tooltip": "Filter resultaten döär bewarkingsetiketten te bruken", + "rcfilters-view-tags-tooltip": "Filter resultaten döär bewarkingsetiketten te gebruken", "rcfilters-liveupdates-button": "Rechtstreakse aktualisering", "rcfilters-liveupdates-button-title-off": "Nye wysigingen voorddalik lÃ¥ten seen", "rcnotefrom": "Wysigingen sinds $3, $4 (maximaal $1 {{PLURAL:$1|wysiging|wysigingen}}).", "rclistfrom": "Bekyk wysigingen vanaf $3 $2", "rcshowhideminor": "$1 kleine wysigingen", - "rcshowhideminor-show": "Bekiek", + "rcshowhideminor-show": "bekyken", "rcshowhideminor-hide": "verbargen", "rcshowhidebots": "$1 botbrukers", "rcshowhidebots-show": "bekyken", - "rcshowhidebots-hide": "Verbarg", - "rcshowhideliu": "$1 registreerde brukers", + "rcshowhidebots-hide": "verbargen", + "rcshowhideliu": "$1 registreerde gebrukers", "rcshowhideliu-show": "Bekiek", "rcshowhideliu-hide": "verbargen", - "rcshowhideanons": "$1 anonyme brukers", - "rcshowhideanons-show": "Bekiek", + "rcshowhideanons": "$1 anonyme gebrukers", + "rcshowhideanons-show": "bekyken", "rcshowhideanons-hide": "verbargen", "rcshowhidepatr": "$1 nao-ekeken bewarkingen", "rcshowhidepatr-show": "Bekiek", "rcshowhidepatr-hide": "Verbarg", "rcshowhidemine": "$1 myn bewarkingen", - "rcshowhidemine-show": "Bekiek", + "rcshowhidemine-show": "bekyken", "rcshowhidemine-hide": "verbargen", "rcshowhidecategorization": "$1 kategorisering van ziejen", "rcshowhidecategorization-show": "Bekiek", @@ -1366,7 +1366,7 @@ "upload_source_file": " (n bestaand op de hardeschieve)", "listfiles-summary": "Op disse spesiale zied ku'j alle bestaanden bekieken die lestens op-estuurd bin.", "listfiles_search_for": "Zeuk naor bestaand:", - "imgfile": "bestaand", + "imgfile": "bestand", "listfiles": "Bestaandslieste", "listfiles_thumb": "Miniatuuraofbeelding", "listfiles_date": "Daotum", @@ -1390,20 +1390,20 @@ "filehist-thumb": "Miniatuurafbealding", "filehist-thumbtext": "Miniatuurafbealding vöär versy van $1", "filehist-nothumb": "Gien miniatuuraofbeelding", - "filehist-user": "Bruker", + "filehist-user": "Gebruker", "filehist-dimensions": "Groutde", "filehist-filesize": "Bestaandsgrootte", "filehist-comment": "Kommentaar", "imagelinks": "Bestandsbruuk", - "linkstoimage": "Dit bestand wördt up de volgende {{PLURAL:$1|syde|$1 syden}} bruked:", - "linkstoimage-more": "Meyr as $1 {{PLURAL:$1|syde bruukt|syden bruken}} dit bestand.\nDe volgende lyste givt allinnig de {{PLURAL:$1|eyrste syde|eyrste $1 syden}} weader dee dit bestand bruukt.\nDe [[Special:WhatLinksHere/$2|heyle lyste]] is ouk beskikbÃ¥r.", - "nolinkstoimage": "Geen enkele syde bruukt dit bestand.", + "linkstoimage": "Dit bestand wördt up de volgende {{PLURAL:$1|syde|$1 syden}} gebruked:", + "linkstoimage-more": "Meyr as $1 {{PLURAL:$1|syde gebruukt|syden gebruken}} dit bestand.\nDe volgende lyste givt allinnig de {{PLURAL:$1|eyrste syde|eyrste $1 syden}} weader dee dit bestand gebruukt.\nDe [[Special:WhatLinksHere/$2|heyle lyste]] is ouk beskikbÃ¥r.", + "nolinkstoimage": "Geen enkele syde gebruukt dit bestand.", "morelinkstoimage": "[[Special:WhatLinksHere/$1|Meer verwiezingen]] naor dit bestaand bekieken.", "linkstoimage-redirect": "$1 (bestaandsdeurverwiezing) $2", "duplicatesoffile": "{{PLURAL:$1|t Volgende bestaand is|De volgende $1 bestaanden bin}} gelieke an dit bestaand ([[Special:FileDuplicateSearch/$2|meer informasie]]):", "sharedupload": "Dit is n edeeld bestaand op $1 en ku'j oek gebruken veur aandere projekten.", "sharedupload-desc-there": "Dit is n edeeld bestaand op $1 en ku'j oek gebruken veur aandere projekten. Bekiek de [$2 beschrieving van t bestaand] veur meer informasie.", - "sharedupload-desc-here": "Dit bestand kümt van $1 en kan ouk in andere projekten bruked weasen. De [$2 syde mid de beskryving van et bestand] steyt hyrunder.", + "sharedupload-desc-here": "Dit bestand kümt van $1 en kan ouk in andere projekten gebruked weasen. De [$2 syde mid de beskryving van et bestand] steyt hyrunder.", "sharedupload-desc-edit": "Dit besatand kömp van $1 en kan oek in aandere projekten gebruukt wörden.\nJe kunnen de [$2 zied mit de bestaandsbeschrieving] daor bewarken.", "sharedupload-desc-create": "Dit besatand kömp van $1 en kan oek in aandere projekten gebruukt wörden.\nJe kunnen de [$2 zied mit de bestaandsbeschrieving] daor bewarken.", "filepage-nofile": "Der besteet gien bestaand mit disse naam.", @@ -1492,7 +1492,7 @@ "ncategories": "$1 {{PLURAL:$1|kategorie|kategorieën}}", "ninterwikis": "$1 {{PLURAL:$1|interwikiverwiezing|interwikiverwiezingen}}", "nlinks": "$1 {{PLURAL:$1|verwiezing|verwiezingen}}", - "nmembers": "$1 {{PLURAL:$1|onderwarp|onderwarpen}}", + "nmembers": "$1 {{PLURAL:$1|lid|leaden}}", "nrevisions": "$1 {{PLURAL:$1|versie|versies}}", "nimagelinks": "Wörden op {{PLURAL:$1|één zied|$1 ziejen}} gebruukt", "ntransclusions": "wörden op {{PLURAL:$1|één zied|$1 ziejen}} gebruukt", @@ -1552,13 +1552,13 @@ "nopagetitle": "Doelzied besteet niet", "nopagetext": "De zied die'j herneumen willen besteet niet.", "pager-newer-n": "{{PLURAL:$1|1 niejere|$1 niejere}}", - "pager-older-n": "{{PLURAL:$1|1 ouwere|$1 ouwere}}", + "pager-older-n": "{{PLURAL:$1|1 oldere|$1 oldere}}", "suppress": "Toezicht", "querypage-disabled": "Disse spesiale zied is uutezet um prestasieredens.", "apisandbox": "API-zaandkule", "booksources": "Bookinformaty", - "booksources-search-legend": "Zeuk informasie over n boek", - "booksources-search": "Zeuken", + "booksources-search-legend": "Söök bronnen van een book", + "booksources-search": "Söken", "booksources-text": "Hieronder steet n lieste mit verwiezingen naor aandere websteeën die nieje of wat ouwere boeken verkopen, en daor hebben ze warschienlik meer informasie over t boek da'j zeuken:", "booksources-invalid-isbn": "De op-egeven ISBN klop niet; kiek effen nao o'j gien fout emaakt hebben bie de invoer.", "magiclink-tracking-isbn": "Ziejen die magiese ISBN-verwiezingen gebruken", @@ -1585,7 +1585,7 @@ "cachedspecial-viewing-cached-ttl": "Je bekieken noen n versie uut t tussengeheugen van disse zied, die hooguut $1 oud is.", "cachedspecial-viewing-cached-ts": "Je bekieken noen n versie uut t tussengeheugen van disse zied, t kan ween dat t niet helemaole bie de tied is.", "cachedspecial-refresh-now": "Leste bekieken.", - "categories": "Kategorieën", + "categories": "Kategoryen", "categoriespagetext": "De de volgende {{PLURAL:$1|kategorie steet|kategorieën staon}} ziejen of mediabestaanden.\n[[Special:UnusedCategories|ongebruukten kategorieën]] zie'j hier niet.\nZie oek [[Special:WantedCategories|gewunste kategorieën]].", "categoriesfrom": "Laot kategorieën zien vanaof:", "deletedcontributions": "Vortedaone gebrukersbiedragen", @@ -1710,7 +1710,7 @@ "actioncomplete": "Uutevoerd", "actionfailed": "De haandeling is mislokt.", "deletedtext": "t Artikel \"$1\" is vortedaon. Zie de \"$2\" veur n lieste van ziejen die as lest vortedaon bin.", - "dellogpage": "Vortdologboek", + "dellogpage": "Vordsmytlogbook", "dellogpagetext": "Hieronder steet n lieste van ziejen en bestaanden die as lest vortedaon bin.", "deletionlog": "Vortdologboek", "reverted": "Eerdere versie hersteld", @@ -1724,7 +1724,7 @@ "deleting-backlinks-warning": "Waorschuwing: [[Special:WhatLinksHere/{{FULLPAGENAME}}|aandere ziejen]] gebruken of verwiezen naor de zied die'j vortdoon willen.", "rollback": "Wiezigingen herstellen", "rollbacklink": "weaderümmedraien", - "rollbacklinkcount": "{{PLURAL:$1|één bewarking|$1 bewarkingen}} weerummedreien", + "rollbacklinkcount": "{{PLURAL:$1|eyn bewarking|$1 bewarkingen}} weaderümmedraien", "rollbacklinkcount-morethan": "Meer as {{PLURAL:$1|één bewarking|$1 bewarkingen}} weerummedreien", "rollbackfailed": "Wieziging herstellen is mislokt", "cantrollback": "De wiezigingen konnen niet hersteld wörden; der is mer 1 auteur.", @@ -1735,7 +1735,7 @@ "rollback-success": "Wiezigingen van $1; weerummedreid naor de leste versie van $2.", "sessionfailure-title": "Sessiefout", "sessionfailure": "Der is n probleem mit joew anmeldsessie. De aksie is stop-ezet uut veurzörg tegen n beveiligingsrisico (dat besteet uut t meugelike \"kraken\" van disse sessie). Gao weerumme naor de veurige zied, laoj disse zied opniej en probeer t nog es.", - "protectlogpage": "Beveiligingslogboek", + "protectlogpage": "Beveiligingslogbook", "protectlogtext": "Hieronder staon de leste wiezigingen veur t blokkeren en vriegeven van artikels en ziejen.\nZie de [[Special:ProtectedPages|lieste mit ziejen die beveiligd bin]] veur t hele overzicht.", "protectedarticle": "[[$1]] is beveiligd", "modifiedarticleprotection": "beveiligingsnivo van \"[[$1]]\" ewiezigd", @@ -1838,8 +1838,8 @@ "contribsub2": "Veur {{GENDER:$3|$1}} ($2)", "nocontribs": "Gien wiezigingen evunnen die an de estelde criteria voldoon.", "uctop": "leste wieziging", - "month": "Maond:", - "year": "Jaor:", + "month": "MÃ¥nd:", + "year": "Vanaf jÃ¥r (en eyrer):", "sp-contributions-blocklog": "blokkeerlogboek", "sp-contributions-deleted": "vortedaone gebrukersbiedragen", "sp-contributions-uploads": "nieje bestaanden", @@ -2130,14 +2130,14 @@ "import-logentry-interwiki-detail": "$1 {{PLURAL:$1|versie|versies}} van $2", "javascripttest": "JavaScript testen", "javascripttest-qunit-intro": "Zie de [$1 testdokumentasie] op mediawiki.org.", - "tooltip-pt-userpage": "{{GENDER:|Juw}} brukerssyde", + "tooltip-pt-userpage": "{{GENDER:|Juw}} gebrukerssyde", "tooltip-pt-anonuserpage": "Gebroekersbladziede vuur t IP-adres da'j broekt", "tooltip-pt-mytalk": "{{GENDER:|Juw}} oaverlegsyde", "tooltip-pt-anontalk": "Oaverlegbladziede van n naamlozen gebroeker van dit IP-adres", "tooltip-pt-preferences": "{{GENDER:|Juw}} instellingen", "tooltip-pt-watchlist": "Lyste van syden dee up myn volglyste stÃ¥n", "tooltip-pt-mycontris": "Oaversicht van {{GENDER:|juw}} bydragen", - "tooltip-pt-login": "Jy wördet van harte uutnöygd üm ju an te melden as bruker, mär et is neet verplicht", + "tooltip-pt-login": "Jy wördet van harte nöygd üm ju an te melden as gebruker, mär et is neet verplichted", "tooltip-pt-logout": "Afmelden", "tooltip-pt-createaccount": "Skryv juw eigen vöäral in en meld juw eigen an. Dit is lykewels neet verplicht.", "tooltip-ca-talk": "LÃ¥t een oaverlegtekst oaver disse syde seen", @@ -2149,7 +2149,7 @@ "tooltip-ca-unprotect": "De beveiliging vuur disse ziede wiezigen", "tooltip-ca-delete": "Smiet disse ziede vort", "tooltip-ca-undelete": "Haal n inhoald van disse ziede oet n emmer", - "tooltip-ca-move": "Gef disse ziede nen aanderen titel", + "tooltip-ca-move": "Disse syde hernömen", "tooltip-ca-watch": "Voog disse syde to an juw volglyste", "tooltip-ca-unwatch": "Smiet disse ziede van oewe voalglieste", "tooltip-search": "{{SITENAME}} döärsöken", @@ -2167,7 +2167,7 @@ "tooltip-t-recentchangeslinked": "Pas verrichte veranderingen dee nÃ¥ disse syde verwyset", "tooltip-feed-rss": "RSS-voer vuur disse ziede", "tooltip-feed-atom": "Atom-voer vuur disse ziede", - "tooltip-t-contributions": "Een lyste mid bydragen van {{GENDER:$1|disse bruker}}", + "tooltip-t-contributions": "Een lyste mid bydragen van {{GENDER:$1|disse gebruker}}", "tooltip-t-emailuser": "Stüür disse {{GENDER:$1|gebruker}} een netpostbericht", "tooltip-t-info": "Meer informasie over disse zied", "tooltip-t-upload": "Laad afbealdingen en/of gelüüdsmateriaal", @@ -2175,10 +2175,10 @@ "tooltip-t-print": "De afdrükbÃ¥re versy van disse syde", "tooltip-t-permalink": "Permanente verwysing nÃ¥ disse versy van de syde", "tooltip-ca-nstab-main": "LÃ¥t een tekst van et artikel seen", - "tooltip-ca-nstab-user": "Brukerssyde bekyken", + "tooltip-ca-nstab-user": "Gebrukerssyde bekyken", "tooltip-ca-nstab-media": "Loat n mediatekst zeen", "tooltip-ca-nstab-special": "Dit is een bysündere syde dee jy neet veranderen künt", - "tooltip-ca-nstab-project": "Loat de projektbladziede zeen", + "tooltip-ca-nstab-project": "Projektsyde bekyken", "tooltip-ca-nstab-image": "LÃ¥t de bestandssyde seen", "tooltip-ca-nstab-mediawiki": "Loat de systeemtekstbladziede zeen", "tooltip-ca-nstab-template": "Mal bekyken", @@ -2194,7 +2194,7 @@ "tooltip-watchlistedit-raw-submit": "Volglieste biewarken", "tooltip-recreate": "Disse ziede opniej anmaken, ondanks t feit dat t vortdoan is.", "tooltip-upload": "Bestaanden opsturen", - "tooltip-rollback": "\"Weaderümmedraien\" drait mid eyn klik de bewarking(en) van de lätste bruker up disse syde terügge.", + "tooltip-rollback": "\"Weaderümmedraien\" drait mid eyn klik de bewarking(en) van de lätste gebruker up disse syde terügge.", "tooltip-undo": "As jy up \"weaderümmedraien\" klikket geyt et bewarkingsveld lös en kün jy een vöärige versy weaderümmesetten. Jy künnet in de bewarkingssamenvatting een readen upgeaven.", "tooltip-preferences-save": "Vuurkeuren opsloan", "tooltip-summary": "Voor een korte samenvatting in", @@ -2281,8 +2281,8 @@ "filedelete-old-unregistered": "De an-egeven bestaandsversie \"$1\" steet niet in de databanke.", "filedelete-current-unregistered": "t An-egeven bestaand \"$1\" steet niet in de databanke.", "filedelete-archive-read-only": "De webserver kan niet in de archiefmap \"$1\" schrieven.", - "previousdiff": "← veurige wieziging", - "nextdiff": "volgende wieziging →", + "previousdiff": "← vöärige wysiging", + "nextdiff": "volgende wysiging →", "mediawarning": "'''Waorschuwing:''' in dit bestaand zit misschien kodering die slicht is veur t systeem.", "imagemaxsize": "Maximale aofmetingen van aofbeeldingen:
\n''(veur op de beschrievingszied)''", "thumbsize": "Grootte van de miniatuuraofbeelding:", @@ -2450,7 +2450,7 @@ "version-entrypoints-header-entrypoint": "Ingang", "version-entrypoints-header-url": "Webadres", "redirect": "Deurverwiezen op bestaandsnaam, gebrukers-, zied-, versie- of logboekregelnummer", - "redirect-summary": "Disse speciale syde verwist döär nå een bestand (as de bestandsname upgeaven wördt), een syde (as een syd- of versynummer upgeaven wördt), een brukerssyde (as et brukersnummer upgeaven wördt) of een logbookinskryving (as een logbooknummer upgeaven wördt). Gebruuk: [[{{#Special:Redirect}}/file/Vöärbeald.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] of [[{{#Special:Redirect}}/logid/186]].", + "redirect-summary": "Disse speciale syde verwist döär nå een bestand (as de bestandsname upgeaven wördt), een syde (as een syd- of versynummer upgeaven wördt), een gebrukerssyde (as et gebrukersnummer upgeaven wördt) of een logbookinskryving (as een logbooknummer upgeaven wördt). Gebruuk: [[{{#Special:Redirect}}/file/Vöärbeald.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] of [[{{#Special:Redirect}}/logid/186]].", "redirect-submit": "Zeuk", "redirect-lookup": "Opzeuken:", "redirect-value": "Weerde:", @@ -2566,7 +2566,7 @@ "logentry-rights-rights": "$1 {{GENDER:$2|hef}} groepslidmaotschap veur $3 ewiezigd van $4 naor $5", "logentry-rights-rights-legacy": "$1 {{GENDER:$2|hef}} t groepslidmaotschap ewiezigd veur $3", "logentry-rights-autopromote": "$1 {{GENDER:$2|is}} automaties bevorderd van $4 tot $5", - "logentry-upload-upload": "$1 hef $3 {{GENDER:$2|op-estuurd}}", + "logentry-upload-upload": "$1 hevt $3 {{GENDER:$2|upladen}}", "logentry-upload-overwrite": "$1 {{GENDER:$2|hef}} n nieje versie van $3 op-elaojen", "rightsnone": "(gien)", "feedback-adding": "Joew kommentaar wörden op de zied ezet...", diff --git a/languages/i18n/nl.json b/languages/i18n/nl.json index 5fb8323591..c67bab321e 100644 --- a/languages/i18n/nl.json +++ b/languages/i18n/nl.json @@ -3913,5 +3913,6 @@ "mycustomjsredirectprotected": "U hebt geen rechten om deze JavaScriptpagina te bewerken omdat het een doorverwijzing is en deze niet verwijst naar een pagina in uw gebruikersruimte.", "easydeflate-invaliddeflate": "De opgegeven inhoud is onjuist gecomprimeerd", "unprotected-js": "Vanwege veiligheidsredenen kan er geen JavaScript geladen worden vanaf onbeveiligde pagina's. Gelieve alleen JavaScript pagina's aan te maken in de MediaWiki: naamruimte of als een subpagina van een gebruikerspagina.", - "userlogout-continue": "Wilt u zich afmelden?" + "userlogout-continue": "Wilt u zich afmelden?", + "rest-wrong-method": "De requestmethode ($1) is {{PLURAL:$3|niet de toegestane methode voor dit pad|een van de toegestane methoden voor dit pad}} ($2)" } diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index afbfd94bab..6b141f909e 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -563,6 +563,7 @@ "perfcached": "Like {{msg-mw|perfcachedts}} but used when we do not know how long ago page was cached (unlikely to happen).\n\nParameters:\n* $1 - the max result cut off ($wgQueryCacheLimit)", "perfcachedts": "Used on pages that list page lists for which the displayed data is cached. Parameters:\n* $1 - a time stamp (date and time combined)\n* $2 - a date (optional)\n* $3 - a time (optional)\n* $4 - the cut off limit for cached results ($wgQueryCacheLimit). If there are more then this many results for the query, only the first $4 of those will be listed on the page. Usually $4 is about 1000.", "querypage-no-updates": "Text on some special pages, e.g. [[Special:FewestRevisions]].", + "querypage-updates-periodical": "Text on some special pages which are configurated with a periodical run of a maintenance script.\n\nSee also {{msg-mw|querypage-no-updates}}.", "viewsource": "The text displayed in place of the {{msg-mw|Edit}} tab when the user has no permission to edit the page.\n\nSee also:\n* {{msg-mw|Viewsource}}\n* {{msg-mw|Accesskey-ca-viewsource}}\n* {{msg-mw|Tooltip-ca-viewsource}}\n{{Identical|View source}}", "viewsource-title": "Page title shown when trying to edit a protected page. Parameters:\n* $1 - the name of the page", "actionthrottled": "This is the title of an error page. Read it in combination with {{msg-mw|actionthrottledtext}}.", diff --git a/languages/i18n/te.json b/languages/i18n/te.json index d3e4af9ab1..83a72dffd8 100644 --- a/languages/i18n/te.json +++ b/languages/i18n/te.json @@ -64,6 +64,7 @@ "tog-watchlisthideminor": "చిన్న మార్పులను నా వీక్షణా జాబితాలో చూపించొద్దు", "tog-watchlisthideliu": "లాగిన్ ఐన వాడుకరులు చేసే మార్పులను వీక్షణా జాబితాలో చూపించకు", "tog-watchlistreloadautomatically": "ఫిల్టరు మారినప్పుడెల్లా వీక్షణ జాబితాను తిరిగి లోడు చెయ్యి (JavaScript అవసరం)", + "tog-watchlistunwatchlinks": "మార్పులు జరిగిన వీక్షణ పేజీలకు నేరుగా వీక్షించు/వద్దు సూచికలను ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) చేర్చు (టాగుల్ చెయాలంటే JavaScript ఆవశ్యకం)", "tog-watchlisthideanons": "అజ్ఞాత వాడుకరుల మార్పులను వీక్షణా జాబితాలో చూపించకు", "tog-watchlisthidepatrolled": "నిఘా ఉన్న మార్పులను వీక్షణజాబితా నుంచి దాచిపెట్టు", "tog-watchlisthidecategorization": "పేజీ వర్గీకరణను దాచు", @@ -74,6 +75,7 @@ "tog-useeditwarning": "ఏదైనా పేజీని నేను వదిలివెళ్తున్నప్పుడు దానిలో భద్రపరచని మార్పులు ఉంటే నన్ను హెచ్చరించు", "tog-prefershttps": "లాగిన్ అయి ఉన్నప్పుడెల్లా భద్ర కనెక్షనునే వాడు", "tog-showrollbackconfirmation": "రోల్‌బ్యాక్ లింకును నొక్కినపుడు నిర్ధారించుకునే సందేశాన్ని చూపించు", + "tog-requireemail": "సంకేతపదం మార్చుకోడానికి ఈమెయిలు ఆవశ్యకం", "underline-always": "ఎల్లప్పుడూ", "underline-never": "ఎప్పటికీ వద్దు", "underline-default": "అలంకారపు లేదా విహారిణి అప్రమేయం", @@ -453,7 +455,7 @@ "createaccountmail": "ఏదో ఒక తాత్కాలిక సంకేతపదాన్ని వాడి దాన్ని పేర్కొన్న ఈమెయిలు చిరునామాకు పంపించు", "createaccountmail-help": "సంకేతపదం తెలుసుకోనవసరం లేకుండా వేరొకరి కోసం ఖాతా సృష్టించేందుకు వాడవచ్చు.", "createacct-realname": "అసలు పేరు (ఐచ్చికం)", - "createacct-reason": "కారణం", + "createacct-reason": "కారణం (అందరికీ కనిపిస్తుంది)", "createacct-reason-ph": "మీరు మరో ఖాతాను ఎందుకు సృష్టించుకుంటున్నారు", "createacct-reason-help": "సృష్టించిన ఖాతాల చిట్టాలో చూపించే సందేశం", "createacct-submit": "మీ ఖాతాను సృష్టించుకోండి", @@ -540,8 +542,10 @@ "botpasswords": "బాట్ సంకేతపదాలు", "botpasswords-disabled": "బాట్ సంకేతపదాలను అచేతనం చేసాం.", "botpasswords-no-central-id": "బాఅత్ సంకేతపదాలను వాడాలంటే, మీరు ఒక కేంద్రీకృత ఖాతాలోకి లాగినవ్వాలి.", + "botpasswords-existing": "ప్రస్తుతం ఉన్న బాట్ సంకేతపదాలు", "botpasswords-createnew": "ఓ కొత్త బాట్ సంకేతపదాన్ని సృష్టించండి", "botpasswords-editexisting": "ఉనికిలో ఉన్న బాట్ సంకేతపదాన్ని మార్చండి", + "botpasswords-label-needsreset": "(సంకేతపదాన్ని మార్చాల్సిన అవసరం ఉంది)", "botpasswords-label-appid": "బాట్ పేరు:", "botpasswords-label-create": "సృష్టించు", "botpasswords-label-update": "తాజాకరించు", diff --git a/maintenance/archives/patch-drop-archive-ar_usertext_timestamp.sql b/maintenance/archives/patch-drop-archive-ar_usertext_timestamp.sql new file mode 100644 index 0000000000..158d9ae4ac --- /dev/null +++ b/maintenance/archives/patch-drop-archive-ar_usertext_timestamp.sql @@ -0,0 +1,7 @@ +-- T233221: The index on `archive` variously known as `ar_usertext_timestamp` +-- and `usertext_timestamp` has a long and sordid history. We're dropping the +-- `ar_user_text` column entirely now (see patch-drop-user-fields.sql), but +-- this index needs special care thanks to said history. + +-- Do not use the /*i*/ thing here! +DROP INDEX ar_usertext_timestamp ON /*_*/archive; diff --git a/maintenance/archives/patch-drop-archive-usertext_timestamp.sql b/maintenance/archives/patch-drop-archive-usertext_timestamp.sql new file mode 100644 index 0000000000..f409e970c5 --- /dev/null +++ b/maintenance/archives/patch-drop-archive-usertext_timestamp.sql @@ -0,0 +1,7 @@ +-- T233221: The index on `archive` variously known as `ar_usertext_timestamp` +-- and `usertext_timestamp` has a long and sordid history. We're dropping the +-- `ar_user_text` column entirely now (see patch-drop-user-fields.sql), but +-- this index needs special care thanks to said history. + +-- Do not use the /*i*/ thing here! +DROP INDEX usertext_timestamp ON /*_*/archive; diff --git a/maintenance/archives/patch-drop-user-fields.sql b/maintenance/archives/patch-drop-user-fields.sql index 7faa593733..1d0b057eed 100644 --- a/maintenance/archives/patch-drop-user-fields.sql +++ b/maintenance/archives/patch-drop-user-fields.sql @@ -4,7 +4,6 @@ -- T188327. Drop old xx_user and xx_user_text fields, and defaults from xx_actor fields. ALTER TABLE /*_*/archive - DROP INDEX /*i*/ar_usertext_timestamp, DROP COLUMN ar_user, DROP COLUMN ar_user_text, ALTER COLUMN ar_actor DROP DEFAULT; diff --git a/maintenance/updateSpecialPages.php b/maintenance/updateSpecialPages.php index 5d756e87a5..7e57f672b4 100644 --- a/maintenance/updateSpecialPages.php +++ b/maintenance/updateSpecialPages.php @@ -42,12 +42,13 @@ class UpdateSpecialPages extends Maintenance { } public function execute() { - global $wgQueryCacheLimit, $wgDisableQueryPageUpdate; + global $wgQueryCacheLimit; $dbw = $this->getDB( DB_MASTER ); $this->doSpecialPageCacheUpdates( $dbw ); + $disabledQueryPages = QueryPage::getDisabledQueryPages( $this->getConfig() ); foreach ( QueryPage::getPages() as $page ) { list( , $special ) = $page; $limit = $page[2] ?? null; @@ -59,7 +60,7 @@ class UpdateSpecialPages extends Maintenance { } if ( !$this->hasOption( 'override' ) - && $wgDisableQueryPageUpdate && in_array( $special, $wgDisableQueryPageUpdate ) + && isset( $disabledQueryPages[$special] ) ) { $this->output( sprintf( "%-30s [QueryPage] disabled\n", $special ) ); continue; diff --git a/resources/Resources.php b/resources/Resources.php index 383c540fdd..0b0e485197 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -2147,6 +2147,7 @@ return [ 'mediawiki.special.contributions' => [ 'scripts' => 'resources/src/mediawiki.special.contributions.js', 'dependencies' => [ + 'jquery.makeCollapsible', 'oojs-ui', 'mediawiki.widgets.DateInputWidget', 'mediawiki.jqueryMsg', diff --git a/tests/phpunit/includes/CommentStoreTest.php b/tests/phpunit/includes/CommentStoreTest.php index 9c08b9f94a..57cc073dfc 100644 --- a/tests/phpunit/includes/CommentStoreTest.php +++ b/tests/phpunit/includes/CommentStoreTest.php @@ -73,6 +73,47 @@ class CommentStoreTest extends MediaWikiLangTestCase { return $store; } + /** + * @dataProvider provideConstructor + * @param int $stage + * @param string|null $exceptionMsg + */ + public function testConstructor( $stage, $exceptionMsg ) { + try { + $m = new CommentStore( Language::factory( 'qqx' ), $stage ); + if ( $exceptionMsg !== null ) { + $this->fail( 'Expected exception not thrown' ); + } + $this->assertInstanceOf( CommentStore::class, $m ); + } catch ( InvalidArgumentException $ex ) { + $this->assertSame( $exceptionMsg, $ex->getMessage() ); + } + } + + public static function provideConstructor() { + return [ + [ 0, '$stage must include a write mode' ], + [ SCHEMA_COMPAT_READ_OLD, '$stage must include a write mode' ], + [ SCHEMA_COMPAT_READ_NEW, '$stage must include a write mode' ], + [ SCHEMA_COMPAT_READ_BOTH, '$stage must include a write mode' ], + + [ SCHEMA_COMPAT_WRITE_OLD, '$stage must include a read mode' ], + [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_OLD, null ], + [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_NEW, null ], + [ SCHEMA_COMPAT_WRITE_OLD | SCHEMA_COMPAT_READ_BOTH, null ], + + [ SCHEMA_COMPAT_WRITE_NEW, '$stage must include a read mode' ], + [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_OLD, null ], + [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_NEW, null ], + [ SCHEMA_COMPAT_WRITE_NEW | SCHEMA_COMPAT_READ_BOTH, null ], + + [ SCHEMA_COMPAT_WRITE_BOTH, '$stage must include a read mode' ], + [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, null ], + [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, null ], + [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_BOTH, null ], + ]; + } + /** * @dataProvider provideGetFields * @param int $stage @@ -115,6 +156,14 @@ class CommentStoreTest extends MediaWikiLangTestCase { MIGRATION_NEW, 'ipb_reason', [ 'ipb_reason_id' => 'ipb_reason_id' ], ], + 'Simple table, write-both/read-old' => [ + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_reason', + [ 'ipb_reason_text' => 'ipb_reason', 'ipb_reason_data' => 'NULL', 'ipb_reason_cid' => 'NULL' ], + ], + 'Simple table, write-both/read-new' => [ + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_reason', + [ 'ipb_reason_id' => 'ipb_reason_id' ], + ], 'Revision, old' => [ MIGRATION_OLD, 'rev_comment', @@ -136,6 +185,18 @@ class CommentStoreTest extends MediaWikiLangTestCase { MIGRATION_NEW, 'rev_comment', [ 'rev_comment_pk' => 'rev_id' ], ], + 'Revision, write-both/read-old' => [ + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_comment', + [ + 'rev_comment_text' => 'rev_comment', + 'rev_comment_data' => 'NULL', + 'rev_comment_cid' => 'NULL', + ], + ], + 'Revision, write-both/read-new' => [ + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_comment', + [ 'rev_comment_pk' => 'rev_id' ], + ], 'Image, old' => [ MIGRATION_OLD, 'img_description', @@ -165,6 +226,20 @@ class CommentStoreTest extends MediaWikiLangTestCase { 'img_description_id' => 'img_description_id' ], ], + 'Image, write-both/read-old' => [ + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'img_description', + [ + 'img_description_text' => 'img_description', + 'img_description_data' => 'NULL', + 'img_description_cid' => 'NULL', + ], + ], + 'Image, write-both/read-new' => [ + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'img_description', + [ + 'img_description_id' => 'img_description_id' + ], + ], ]; } @@ -244,6 +319,30 @@ class CommentStoreTest extends MediaWikiLangTestCase { ], ], ], + 'Simple table, write-both/read-old' => [ + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'ipb_reason', [ + 'tables' => [], + 'fields' => [ + 'ipb_reason_text' => 'ipb_reason', + 'ipb_reason_data' => 'NULL', + 'ipb_reason_cid' => 'NULL', + ], + 'joins' => [], + ], + ], + 'Simple table, write-both/read-new' => [ + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'ipb_reason', [ + 'tables' => [ 'comment_ipb_reason' => 'comment' ], + 'fields' => [ + 'ipb_reason_text' => 'comment_ipb_reason.comment_text', + 'ipb_reason_data' => 'comment_ipb_reason.comment_data', + 'ipb_reason_cid' => 'comment_ipb_reason.comment_id', + ], + 'joins' => [ + 'comment_ipb_reason' => [ 'JOIN', 'comment_ipb_reason.comment_id = ipb_reason_id' ], + ], + ], + ], 'Revision, old' => [ MIGRATION_OLD, 'rev_comment', [ @@ -310,6 +409,35 @@ class CommentStoreTest extends MediaWikiLangTestCase { ], ], ], + 'Revision, write-both/read-old' => [ + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'rev_comment', [ + 'tables' => [], + 'fields' => [ + 'rev_comment_text' => 'rev_comment', + 'rev_comment_data' => 'NULL', + 'rev_comment_cid' => 'NULL', + ], + 'joins' => [], + ], + ], + 'Revision, write-both/read-new' => [ + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'rev_comment', [ + 'tables' => [ + 'temp_rev_comment' => 'revision_comment_temp', + 'comment_rev_comment' => 'comment', + ], + 'fields' => [ + 'rev_comment_text' => 'comment_rev_comment.comment_text', + 'rev_comment_data' => 'comment_rev_comment.comment_data', + 'rev_comment_cid' => 'comment_rev_comment.comment_id', + ], + 'joins' => [ + 'temp_rev_comment' => [ 'JOIN', 'temp_rev_comment.revcomment_rev = rev_id' ], + 'comment_rev_comment' => [ 'JOIN', + 'comment_rev_comment.comment_id = temp_rev_comment.revcomment_comment_id' ], + ], + ], + ], 'Image, old' => [ MIGRATION_OLD, 'img_description', [ @@ -373,6 +501,34 @@ class CommentStoreTest extends MediaWikiLangTestCase { ], ], ], + 'Image, write-both/read-old' => [ + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, 'img_description', [ + 'tables' => [], + 'fields' => [ + 'img_description_text' => 'img_description', + 'img_description_data' => 'NULL', + 'img_description_cid' => 'NULL', + ], + 'joins' => [], + ], + ], + 'Image, write-both/read-new' => [ + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, 'img_description', [ + 'tables' => [ + 'comment_img_description' => 'comment', + ], + 'fields' => [ + 'img_description_text' => 'comment_img_description.comment_text', + 'img_description_data' => 'comment_img_description.comment_data', + 'img_description_cid' => 'comment_img_description.comment_id', + ], + 'joins' => [ + 'comment_img_description' => [ 'JOIN', + 'comment_img_description.comment_id = img_description_id', + ], + ], + ], + ], ]; } @@ -413,6 +569,15 @@ class CommentStoreTest extends MediaWikiLangTestCase { MIGRATION_NEW ], MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ], MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ], + + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => [ + MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW + ], + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => [ + MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW + ], ]; foreach ( $stages as $writeStage => $possibleReadStages ) { @@ -427,12 +592,12 @@ class CommentStoreTest extends MediaWikiLangTestCase { $fields = $wstore->insert( $this->db, $key, $comment, $data ); } - if ( $writeStage <= MIGRATION_WRITE_BOTH ) { + if ( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) { $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" ); } else { $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" ); } - if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) { + if ( ( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) && !$usesTemp ) { $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" ); } else { $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" ); @@ -463,13 +628,17 @@ class CommentStoreTest extends MediaWikiLangTestCase { $queryInfo['joins'] ); + $expectForCombination = ( + ( $writeStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_OLD || + ( $readStage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD + ) ? $expectOld : $expect; $this->assertComment( - $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect, + $expectForCombination, $rstore->getCommentLegacy( $this->db, $key, $fieldRow ), "w=$writeStage, r=$readStage, from getFields()" ); $this->assertComment( - $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect, + $expectForCombination, $rstore->getComment( $key, $joinRow ), "w=$writeStage, r=$readStage, from getJoin()" ); @@ -503,6 +672,15 @@ class CommentStoreTest extends MediaWikiLangTestCase { MIGRATION_NEW ], MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ], MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW, MIGRATION_NEW ], + + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD => [ + MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW + ], + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW => [ + MIGRATION_OLD, SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD, + SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW, MIGRATION_NEW + ], ]; foreach ( $stages as $writeStage => $possibleReadStages ) { @@ -517,12 +695,12 @@ class CommentStoreTest extends MediaWikiLangTestCase { $fields = $wstore->insert( $this->db, $comment, $data ); } - if ( $writeStage <= MIGRATION_WRITE_BOTH ) { + if ( $writeStage & SCHEMA_COMPAT_WRITE_OLD ) { $this->assertSame( $expect['text'], $fields[$key], "old field, stage=$writeStage" ); } else { $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" ); } - if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) { + if ( ( $writeStage & SCHEMA_COMPAT_WRITE_NEW ) && !$usesTemp ) { $this->assertArrayHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" ); } else { $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" ); @@ -553,13 +731,17 @@ class CommentStoreTest extends MediaWikiLangTestCase { $queryInfo['joins'] ); + $expectForCombination = ( + ( $writeStage & SCHEMA_COMPAT_WRITE_BOTH ) === SCHEMA_COMPAT_WRITE_OLD || + ( $readStage & SCHEMA_COMPAT_READ_BOTH ) === SCHEMA_COMPAT_READ_OLD + ) ? $expectOld : $expect; $this->assertComment( - $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect, + $expectForCombination, $rstore->getCommentLegacy( $this->db, $fieldRow ), "w=$writeStage, r=$readStage, from getFields()" ); $this->assertComment( - $writeStage === MIGRATION_OLD || $readStage === MIGRATION_OLD ? $expectOld : $expect, + $expectForCombination, $rstore->getComment( $joinRow ), "w=$writeStage, r=$readStage, from getJoin()" ); @@ -725,6 +907,9 @@ class CommentStoreTest extends MediaWikiLangTestCase { 'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ], 'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ], 'MIGRATION_NEW' => [ MIGRATION_NEW ], + + 'SCHEMA_COMPAT write-both/read-old' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD ], + 'SCHEMA_COMPAT write-both/read-new' => [ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW ], ]; } diff --git a/tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php b/tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php index 4040ffc76e..6bf219dc7b 100644 --- a/tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php +++ b/tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php @@ -2076,8 +2076,10 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase { /** @var Revision $rev1 */ $rev1 = $editStatus->getValue()['revision']; - $this->setExpectedException( InvalidArgumentException::class ); - MediaWikiServices::getInstance()->getRevisionStore() + $status = MediaWikiServices::getInstance()->getRevisionStore() ->newRevisionsFromBatch( [ $this->revisionToRow( $rev1 ), $this->revisionToRow( $rev1 ) ] ); + + $this->assertFalse( $status->isGood() ); + $this->assertTrue( $status->hasMessage( 'internalerror' ) ); } } diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php index 4c92545128..6fff68feb9 100644 --- a/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php +++ b/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php @@ -316,8 +316,8 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase { * @dataProvider provideCommonDomainGTIDs * @covers Wikimedia\Rdbms\MySQLMasterPos */ - public function testCommonGtidDomains( MySQLMasterPos $pos, MySQLMasterPos $ref, $gtids ) { - $this->assertEquals( $gtids, MySQLMasterPos::getCommonDomainGTIDs( $pos, $ref ) ); + public function testGetRelevantActiveGTIDs( MySQLMasterPos $pos, MySQLMasterPos $ref, $gtids ) { + $this->assertEquals( $gtids, MySQLMasterPos::getRelevantActiveGTIDs( $pos, $ref ) ); } public static function provideCommonDomainGTIDs() { @@ -327,6 +327,12 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase { new MySQLMasterPos( '255-11-1000', 1 ), [ '255-13-99' ] ], + [ + ( new MySQLMasterPos( '255-13-99,256-12-50,257-14-50', 1 ) ) + ->setActiveDomain( 257 ), + new MySQLMasterPos( '255-11-1000,257-14-30', 1 ), + [ '257-14-50' ] + ], [ new MySQLMasterPos( '2E11FA47-71CA-11E1-9E33-C80AA9429562:1-5,' . diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php index dad9f1ed4f..6ebbc3a036 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php @@ -292,9 +292,8 @@ TEXT ->disableOriginalConstructor() ->getMock(); $image->method( 'getDataUri' ) - ->will( $this->returnValue( $dataUriReturnValue ) ); - $image->expects( $this->any() ) - ->method( 'getUrl' ) + ->willReturn( $dataUriReturnValue ); + $image->method( 'getUrl' ) ->will( $this->returnValueMap( [ [ $context, 'load.php', null, 'original', 'original.svg' ], [ $context, 'load.php', null, 'rasterized', 'rasterized.png' ], diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php deleted file mode 100644 index c3fc55acc1..0000000000 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderImageTest.php +++ /dev/null @@ -1,136 +0,0 @@ -imagesPath = __DIR__ . '/../../data/resourceloader'; - } - - protected function getTestImage( $name ) { - $options = ResourceLoaderImageModuleTest::$commonImageData[$name]; - $fileDescriptor = is_string( $options ) ? $options : $options['file']; - $allowedVariants = ( is_array( $options ) && isset( $options['variants'] ) ) ? - $options['variants'] : []; - $variants = array_fill_keys( $allowedVariants, [ 'color' => 'red' ] ); - return new ResourceLoaderImageTestable( - $name, - 'test', - $fileDescriptor, - $this->imagesPath, - $variants - ); - } - - public static function provideGetPath() { - return [ - [ 'abc', 'en', 'abc.gif' ], - [ 'abc', 'he', 'abc.gif' ], - [ 'def', 'en', 'def.svg' ], - [ 'def', 'he', 'def.svg' ], - [ 'ghi', 'en', 'ghi.svg' ], - [ 'ghi', 'he', 'jkl.svg' ], - [ 'mno', 'en', 'mno-ltr.svg' ], - [ 'mno', 'ar', 'mno-rtl.svg' ], - [ 'mno', 'he', 'mno-ltr.svg' ], - [ 'pqr', 'en', 'pqr-b.svg' ], - [ 'pqr', 'en-gb', 'pqr-b.svg' ], - [ 'pqr', 'de', 'pqr-f.svg' ], - [ 'pqr', 'de-formal', 'pqr-f.svg' ], - [ 'pqr', 'ar', 'pqr-f.svg' ], - [ 'pqr', 'fr', 'pqr-a.svg' ], - [ 'pqr', 'he', 'pqr-a.svg' ], - ]; - } - - /** - * @covers ResourceLoaderImage::getPath - * @dataProvider provideGetPath - */ - public function testGetPath( $imageName, $languageCode, $path ) { - static $dirMap = [ - 'en' => 'ltr', - 'en-gb' => 'ltr', - 'de' => 'ltr', - 'de-formal' => 'ltr', - 'fr' => 'ltr', - 'he' => 'rtl', - 'ar' => 'rtl', - ]; - - $image = $this->getTestImage( $imageName ); - $context = $this->getResourceLoaderContext( [ - 'lang' => $languageCode, - 'dir' => $dirMap[$languageCode], - ] ); - - $this->assertEquals( $image->getPath( $context ), $this->imagesPath . '/' . $path ); - } - - /** - * @covers ResourceLoaderImage::getExtension - * @covers ResourceLoaderImage::getMimeType - */ - public function testGetExtension() { - $image = $this->getTestImage( 'def' ); - $this->assertEquals( $image->getExtension(), 'svg' ); - $this->assertEquals( $image->getExtension( 'original' ), 'svg' ); - $this->assertEquals( $image->getExtension( 'rasterized' ), 'png' ); - $image = $this->getTestImage( 'abc' ); - $this->assertEquals( $image->getExtension(), 'gif' ); - $this->assertEquals( $image->getExtension( 'original' ), 'gif' ); - $this->assertEquals( $image->getExtension( 'rasterized' ), 'gif' ); - } - - /** - * @covers ResourceLoaderImage::getImageData - * @covers ResourceLoaderImage::variantize - * @covers ResourceLoaderImage::massageSvgPathdata - */ - public function testGetImageData() { - $context = $this->getResourceLoaderContext(); - - $image = $this->getTestImage( 'def' ); - $data = file_get_contents( $this->imagesPath . '/def.svg' ); - $dataConstructive = file_get_contents( $this->imagesPath . '/def_variantize.svg' ); - $this->assertEquals( $image->getImageData( $context, null, 'original' ), $data ); - $this->assertEquals( - $image->getImageData( $context, 'destructive', 'original' ), - $dataConstructive - ); - // Stub, since we don't know if we even have a SVG handler, much less what exactly it'll output - $this->assertEquals( $image->getImageData( $context, null, 'rasterized' ), 'RASTERIZESTUB' ); - - $image = $this->getTestImage( 'abc' ); - $data = file_get_contents( $this->imagesPath . '/abc.gif' ); - $this->assertEquals( $image->getImageData( $context, null, 'original' ), $data ); - $this->assertEquals( $image->getImageData( $context, null, 'rasterized' ), $data ); - } - - /** - * @covers ResourceLoaderImage::massageSvgPathdata - */ - public function testMassageSvgPathdata() { - $image = $this->getTestImage( 'ghi' ); - $data = file_get_contents( $this->imagesPath . '/ghi.svg' ); - $dataMassaged = file_get_contents( $this->imagesPath . '/ghi_massage.svg' ); - $this->assertEquals( $image->massageSvgPathdata( $data ), $dataMassaged ); - } -} - -class ResourceLoaderImageTestable extends ResourceLoaderImage { - // Make some protected methods public - public function massageSvgPathdata( $svg ) { - return parent::massageSvgPathdata( $svg ); - } - - // Stub, since we don't know if we even have a SVG handler, much less what exactly it'll output - public function rasterize( $svg ) { - return 'RASTERIZESTUB'; - } -} diff --git a/tests/phpunit/unit/includes/resourceloader/ResourceLoaderImageTest.php b/tests/phpunit/unit/includes/resourceloader/ResourceLoaderImageTest.php new file mode 100644 index 0000000000..5265b3ee17 --- /dev/null +++ b/tests/phpunit/unit/includes/resourceloader/ResourceLoaderImageTest.php @@ -0,0 +1,148 @@ +imagesPath = __DIR__ . '/../../../data/resourceloader'; + } + + protected function tearDown() { + Language::$dataCache = null; + } + + protected function getTestImage( $name ) { + $options = ResourceLoaderImageModuleTest::$commonImageData[$name]; + $fileDescriptor = is_string( $options ) ? $options : $options['file']; + $allowedVariants = ( is_array( $options ) && isset( $options['variants'] ) ) ? + $options['variants'] : []; + $variants = array_fill_keys( $allowedVariants, [ 'color' => 'red' ] ); + return new ResourceLoaderImageTestable( + $name, + 'test', + $fileDescriptor, + $this->imagesPath, + $variants + ); + } + + public static function provideGetPath() { + return [ + [ 'abc', 'en', 'abc.gif' ], + [ 'abc', 'he', 'abc.gif' ], + [ 'def', 'en', 'def.svg' ], + [ 'def', 'he', 'def.svg' ], + [ 'ghi', 'en', 'ghi.svg' ], + [ 'ghi', 'he', 'jkl.svg' ], + [ 'mno', 'en', 'mno-ltr.svg' ], + [ 'mno', 'ar', 'mno-rtl.svg' ], + [ 'mno', 'he', 'mno-ltr.svg' ], + [ 'pqr', 'en', 'pqr-b.svg' ], + [ 'pqr', 'en-gb', 'pqr-b.svg' ], + [ 'pqr', 'de', 'pqr-f.svg' ], + [ 'pqr', 'de-formal', 'pqr-f.svg' ], + [ 'pqr', 'ar', 'pqr-f.svg' ], + [ 'pqr', 'fr', 'pqr-a.svg' ], + [ 'pqr', 'he', 'pqr-a.svg' ], + ]; + } + + /** + * @covers ResourceLoaderImage::getPath + * @dataProvider provideGetPath + */ + public function testGetPath( $imageName, $languageCode, $path ) { + static $dirMap = [ + 'en' => 'ltr', + 'en-gb' => 'ltr', + 'de' => 'ltr', + 'de-formal' => 'ltr', + 'fr' => 'ltr', + 'he' => 'rtl', + 'ar' => 'rtl', + ]; + + $image = $this->getTestImage( $imageName ); + $context = new DerivativeResourceLoaderContext( + $this->createMock( ResourceLoaderContext::class ) + ); + $context->setLanguage( $languageCode ); + $context->setDirection( $dirMap[$languageCode] ); + Language::$dataCache = $this->createMock( LocalisationCache::class ); + Language::$dataCache->method( 'getItem' )->will( $this->returnCallback( function ( $code ) { + return ( [ + 'en-gb' => [ 'en' ], + 'de-formal' => [ 'de' ], + ] )[ $code ] ?? []; + } ) ); + + $this->assertEquals( $image->getPath( $context ), $this->imagesPath . '/' . $path ); + } + + /** + * @covers ResourceLoaderImage::getExtension + * @covers ResourceLoaderImage::getMimeType + */ + public function testGetExtension() { + $image = $this->getTestImage( 'def' ); + $this->assertEquals( $image->getExtension(), 'svg' ); + $this->assertEquals( $image->getExtension( 'original' ), 'svg' ); + $this->assertEquals( $image->getExtension( 'rasterized' ), 'png' ); + $image = $this->getTestImage( 'abc' ); + $this->assertEquals( $image->getExtension(), 'gif' ); + $this->assertEquals( $image->getExtension( 'original' ), 'gif' ); + $this->assertEquals( $image->getExtension( 'rasterized' ), 'gif' ); + } + + /** + * @covers ResourceLoaderImage::getImageData + * @covers ResourceLoaderImage::variantize + * @covers ResourceLoaderImage::massageSvgPathdata + */ + public function testGetImageData() { + $context = $this->createMock( ResourceLoaderContext::class ); + + $image = $this->getTestImage( 'def' ); + $data = file_get_contents( $this->imagesPath . '/def.svg' ); + $dataConstructive = file_get_contents( $this->imagesPath . '/def_variantize.svg' ); + $this->assertEquals( $image->getImageData( $context, null, 'original' ), $data ); + $this->assertEquals( + $image->getImageData( $context, 'destructive', 'original' ), + $dataConstructive + ); + // Stub, since we don't know if we even have a SVG handler, much less what exactly it'll output + $this->assertEquals( $image->getImageData( $context, null, 'rasterized' ), 'RASTERIZESTUB' ); + + $image = $this->getTestImage( 'abc' ); + $data = file_get_contents( $this->imagesPath . '/abc.gif' ); + $this->assertEquals( $image->getImageData( $context, null, 'original' ), $data ); + $this->assertEquals( $image->getImageData( $context, null, 'rasterized' ), $data ); + } + + /** + * @covers ResourceLoaderImage::massageSvgPathdata + */ + public function testMassageSvgPathdata() { + $image = $this->getTestImage( 'ghi' ); + $data = file_get_contents( $this->imagesPath . '/ghi.svg' ); + $dataMassaged = file_get_contents( $this->imagesPath . '/ghi_massage.svg' ); + $this->assertEquals( $image->massageSvgPathdata( $data ), $dataMassaged ); + } +} + +class ResourceLoaderImageTestable extends ResourceLoaderImage { + // Make some protected methods public + public function massageSvgPathdata( $svg ) { + return parent::massageSvgPathdata( $svg ); + } + + // Stub, since we don't know if we even have a SVG handler, much less what exactly it'll output + public function rasterize( $svg ) { + return 'RASTERIZESTUB'; + } +}