/maintenance/dev/data
/AdminSettings.php
/LocalSettings.php
-/StartProfiler.php
# Building & testing
npm-debug.log
<exclude name="MediaWiki.Commenting.MissingCovers.MissingCovers" />
<exclude name="MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName" />
<exclude name="MediaWiki.Usage.DbrQueryUsage.DbrQueryFound" />
- <exclude name="MediaWiki.Usage.ExtendClassUsage.FunctionVarUsage" />
<exclude name="MediaWiki.Usage.ForbiddenFunctions.passthru" />
<exclude name="MediaWiki.VariableAnalysis.ForbiddenGlobalVariables.ForbiddenGlobal$wgTitle" />
<exclude name="MediaWiki.WhiteSpace.SpaceBeforeSingleLineComment.NewLineComment" />
<exclude-pattern type="relative">^skins/</exclude-pattern>
<exclude-pattern>AdminSettings\.php</exclude-pattern>
<exclude-pattern>LocalSettings\.php</exclude-pattern>
- <exclude-pattern>StartProfiler\.php</exclude-pattern>
</ruleset>
* The $wgPasswordSenderName setting, ignored since 1.23 by MediaWiki and almost
all extensions, is no longer set at all. Instead, you can modify the system
message `emailsender`.
+* A new configuration setting, $wgRawHtmlMessages, is added, for listing
+ messages which are displayed as raw HTML.
=== New features in 1.32 ===
* (T112474) Generalized the ResourceLoader mechanism for overriding modules
render diffs between two Content objects, and DifferenceEngine::setRevisions()
to render diffs between two custom (potentially multi-content) revisions.
Added GetSlotDiffRenderer hook which works like GetDifferenceEngine for slots.
+* Added a temporary action=mcrundo to the web UI, as the normal undo logic
+ can't yet handle MCR and deadlines are forcing is to put off fixing that.
+ This action should be considered deprecated and should not be used directly.
+* Extensions overriding ContentHandler::getUndoContent() will need to be
+ updated for the changed method signature.
=== External library changes in 1.32 ===
* …
* action=query&prop=deletedrevisions, action=query&list=allrevisions, and
action=query&list=alldeletedrevisions are changed similarly to
&prop=revisions (see the three previous items).
+* (T174032) action=compare now supports multi-content revisions.
+ * It has a 'slots' parameter to select diffing of individual slots. The
+ default behavior is to return one combined diff.
+ * The 'fromtext', 'fromsection', 'fromcontentmodel', 'fromcontentformat',
+ 'totext', 'tosection', 'tocontentmodel', and 'tocontentformat' parameters
+ are deprecated. Specify the new 'fromslots' and 'toslots' to identify which
+ slots have text supplied and the corresponding templated parameters for
+ each slot.
+ * The behavior of 'fromsection' and 'tosection' of extracting one section's
+ content is not being preserved. 'fromsection-{slot}' and 'tosection-{slot}'
+ instead expand the given text as if for a section edit. This effectively
+ declines T183823 in favor of T185723.
=== Action API internal changes in 1.32 ===
* Added 'ApiParseMakeOutputPage' hook.
a no-op function since 1.30.
* SpecialPageFactory::resetList() is a no-op. Call overrideMwServices()
instead.
+* MediaWiki no longer supports a StartProfiler.php file.
+ Define $wgProfiler via LocalSettings.php instead.
=== Deprecations in 1.32 ===
-* Use of a StartProfiler.php file is deprecated in favour of placing
- configuration in LocalSettings.php.
* HTMLForm::setSubmitProgressive() is deprecated. No need to call it. Submit
button is already marked as progressive.
* Skin::setupSkinUserCss() is deprecated. Adding of modules to load
MediaWikiServices.
* mw.user.stickyRandomId was renamed to the more explicit
mw.user.getPageviewToken to better capture its function.
+* Passing Revision objects to ContentHandler::getUndoContent() is deprecated,
+ Content object should be passed instead.
=== Other changes in 1.32 ===
* (T198811) The following tables have had their UNIQUE indexes turned into
if ( !$processor instanceof ApiMain ) {
throw new MWException( 'ApiBeforeMain hook set $processor to a non-ApiMain class' );
}
-} catch ( Exception $e ) {
+} catch ( Exception $e ) { // @todo Remove this block when HHVM is no longer supported
+ // Crap. Try to report the exception in API format to be friendly to clients.
+ ApiMain::handleApiBeforeMainException( $e );
+ $processor = false;
+} catch ( Throwable $e ) {
// Crap. Try to report the exception in API format to be friendly to clients.
ApiMain::handleApiBeforeMainException( $e );
$processor = false;
try {
$manager = $processor->getModuleManager();
$module = $manager->getModule( $wgRequest->getVal( 'action' ), 'action' );
- } catch ( Exception $ex ) {
+ } catch ( Exception $ex ) { // @todo Remove this block when HHVM is no longer supported
+ $module = null;
+ } catch ( Throwable $ex ) {
$module = null;
}
if ( !$module || $module->mustBePosted() ) {
'MappedIterator' => __DIR__ . '/includes/libs/MappedIterator.php',
'MarkpatrolledAction' => __DIR__ . '/includes/actions/MarkpatrolledAction.php',
'McTest' => __DIR__ . '/maintenance/mctest.php',
+ 'McrUndoAction' => __DIR__ . '/includes/actions/McrUndoAction.php',
'MediaHandler' => __DIR__ . '/includes/media/MediaHandler.php',
'MediaHandlerFactory' => __DIR__ . '/includes/media/MediaHandlerFactory.php',
'MediaStatisticsPage' => __DIR__ . '/includes/specials/SpecialMediaStatistics.php',
'MediaWiki\\Special\\SpecialPageFactory' => __DIR__ . '/includes/specialpage/SpecialPageFactory.php',
'MediaWiki\\User\\UserIdentity' => __DIR__ . '/includes/user/UserIdentity.php',
'MediaWiki\\User\\UserIdentityValue' => __DIR__ . '/includes/user/UserIdentityValue.php',
+ 'MediaWiki\\Widget\\CheckMatrixWidget' => __DIR__ . '/includes/widget/CheckMatrixWidget.php',
'MediaWiki\\Widget\\ComplexNamespaceInputWidget' => __DIR__ . '/includes/widget/ComplexNamespaceInputWidget.php',
'MediaWiki\\Widget\\ComplexTitleInputWidget' => __DIR__ . '/includes/widget/ComplexTitleInputWidget.php',
'MediaWiki\\Widget\\DateInputWidget' => __DIR__ . '/includes/widget/DateInputWidget.php',
'ThrottledError' => __DIR__ . '/includes/exception/ThrottledError.php',
'ThumbnailImage' => __DIR__ . '/includes/media/MediaTransformOutput.php',
'ThumbnailRenderJob' => __DIR__ . '/includes/jobqueue/jobs/ThumbnailRenderJob.php',
- 'TidyUpBug37714' => __DIR__ . '/maintenance/tidyUpBug37714.php',
+ 'TidyUpT39714' => __DIR__ . '/maintenance/tidyUpT39714.php',
'TiffHandler' => __DIR__ . '/includes/media/TiffHandler.php',
'Timing' => __DIR__ . '/includes/libs/Timing.php',
'Title' => __DIR__ . '/includes/Title.php',
"require-dev": {
"cache/integration-tests": "0.16.0",
"composer/spdx-licenses": "1.4.0",
+ "giorgiosironi/eris": "^0.10.0",
"hamcrest/hamcrest-php": "^2.0",
"jakub-onderka/php-parallel-lint": "0.9.2",
"jetbrains/phpstorm-stubs": "dev-master#38ff1a581b297f7901e961b8c923862ea80c3b96",
"type": "string"
}
},
+ "RawHtmlMessages": {
+ "type": "array",
+ "description": "Messages which are rendered as raw HTML",
+ "items": {
+ "type": "string"
+ }
+ },
"callback": {
"type": [
"array",
"type": "string"
}
},
+ "RawHtmlMessages": {
+ "type": "array",
+ "description": "Messages which are rendered as raw HTML",
+ "items": {
+ "type": "string"
+ }
+ },
"callback": {
"type": [
"array",
*/
$wgResourceLoaderValidateJS = true;
+/**
+ * When enabled, execution of JavaScript modules is profiled client-side.
+ *
+ * Instrumentation happens in mw.loader.profiler.
+ * Use `mw.inspect('time')` from the browser console to display the data.
+ *
+ * @since 1.32
+ */
+$wgResourceLoaderEnableJSProfiler = false;
+
/**
* Whether ResourceLoader should attempt to persist modules in localStorage on
* browsers that support the Web Storage API.
$wgEnableScaryTranscluding = false;
/**
- * Expiry time for transcluded templates cached in transcache database table.
+ * Expiry time for transcluded templates cached in object cache.
* Only used $wgEnableInterwikiTranscluding is set to true.
*/
$wgTranscludeCacheExpiry = 3600;
* Profiler configuration.
*
* To use a profiler, set $wgProfiler in LocalSetings.php.
- * For backwards-compatibility, it is also allowed to set the variable from
- * a separate file called StartProfiler.php, which MediaWiki will include.
*
* Example:
*
'history' => true,
'info' => true,
'markpatrolled' => true,
+ 'mcrundo' => McrUndoAction::class,
'protect' => true,
'purge' => true,
'raw' => true,
*/
$wgCSPReportOnlyHeader = false;
+/**
+ * List of messages which might contain raw HTML.
+ * Extensions should add their messages here. The list is used for access control:
+ * changing messages listed here will require editsitecss and editsitejs rights.
+ *
+ * @since 1.32
+ * @var string[]
+ */
+$wgRawHtmlMessages = [
+ 'copyright',
+ 'history_copyright',
+ 'googlesearch',
+ 'feedback-terms',
+ 'feedback-termsofuse',
+];
+
/**
* Mapping of event channels (or channel categories) to EventRelayer configuration.
*
# checking, etc.
if ( 'initial' == $this->formtype || $this->firsttime ) {
if ( $this->initialiseForm() === false ) {
- $this->noSuchSectionPage();
+ $out = $this->context->getOutput();
+ if ( $out->getRedirect() === '' ) { // mcrundo hack redirects, don't override it
+ $this->noSuchSectionPage();
+ }
return;
}
!$oldrev->isDeleted( Revision::DELETED_TEXT )
) {
if ( WikiPage::hasDifferencesOutsideMainSlot( $undorev, $oldrev ) ) {
- // Cannot yet undo edits that involve anything other the main slot.
- $undoMsg = 'main-slot-only';
+ // Hack for undo while EditPage can't handle multi-slot editing
+ $this->context->getOutput()->redirect( $this->mTitle->getFullURL( [
+ 'action' => 'mcrundo',
+ 'undo' => $undo,
+ 'undoafter' => $undoafter,
+ ] ) );
+ return false;
} else {
$content = $this->page->getUndoContent( $undorev, $oldrev );
$s = $thumb->toHtml( $params );
}
if ( $frameParams['align'] != '' ) {
- $s = "<div class=\"float{$frameParams['align']}\">{$s}</div>";
+ $s = Html::rawElement(
+ 'div',
+ [ 'class' => 'float' . $frameParams['align'] ],
+ $s
+ );
}
return str_replace( "\n", ' ', $prefix . $s . $postfix );
}
if ( !$this->mArticleBodyOnly ) {
$sk = $this->getSkin();
-
- if ( $sk->shouldPreloadLogo() ) {
- $this->addLogoPreloadLinkHeaders();
- }
}
$linkHeader = $this->getLinkHeader();
] );
}
- /**
- * Add Link headers for preloading the wiki's logo.
- *
- * @since 1.26
- */
- protected function addLogoPreloadLinkHeaders() {
- $logo = ResourceLoaderSkinModule::getLogo( $this->getConfig() );
-
- $tags = [];
- $logosPerDppx = [];
- $logos = [];
-
- if ( !is_array( $logo ) ) {
- // No media queries required if we only have one variant
- $this->addLinkHeader( '<' . $logo . '>;rel=preload;as=image' );
- return;
- }
-
- if ( isset( $logo['svg'] ) ) {
- // No media queries required if we only have a 1x and svg variant
- // because all preload-capable browsers support SVGs
- $this->addLinkHeader( '<' . $logo['svg'] . '>;rel=preload;as=image' );
- return;
- }
-
- foreach ( $logo as $dppx => $src ) {
- // Keys are in this format: "1.5x"
- $dppx = substr( $dppx, 0, -1 );
- $logosPerDppx[$dppx] = $src;
- }
-
- // Because PHP can't have floats as array keys
- uksort( $logosPerDppx, function ( $a , $b ) {
- $a = floatval( $a );
- $b = floatval( $b );
- // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
- return $a <=> $b;
- } );
-
- foreach ( $logosPerDppx as $dppx => $src ) {
- $logos[] = [ 'dppx' => $dppx, 'src' => $src ];
- }
-
- $logosCount = count( $logos );
- // Logic must match ResourceLoaderSkinModule:
- // - 1x applies to resolution < 1.5dppx
- // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
- // - 2x applies to resolution >= 2dppx
- // Note that min-resolution and max-resolution are both inclusive.
- for ( $i = 0; $i < $logosCount; $i++ ) {
- if ( $i === 0 ) {
- // Smallest dppx
- // min-resolution is ">=" (larger than or equal to)
- // "not min-resolution" is essentially "<"
- $media_query = 'not all and (min-resolution: ' . $logos[ 1 ]['dppx'] . 'dppx)';
- } elseif ( $i !== $logosCount - 1 ) {
- // In between
- // Media query expressions can only apply "not" to the entire expression
- // (e.g. can't express ">= 1.5 and not >= 2).
- // Workaround: Use <= 1.9999 in place of < 2.
- $upper_bound = floatval( $logos[ $i + 1 ]['dppx'] ) - 0.000001;
- $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] .
- 'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
- } else {
- // Largest dppx
- $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] . 'dppx)';
- }
-
- $this->addLinkHeader(
- '<' . $logos[$i]['src'] . '>;rel=preload;as=image;media=' . $media_query
- );
- }
- }
-
/**
* Get (and set if not yet set) the CSP nonce.
*
* Load LocalSettings.php
*/
-if ( is_readable( "$IP/StartProfiler.php" ) ) {
- // @deprecated since 1.32: Use LocalSettings.php instead.
- require "$IP/StartProfiler.php";
-}
-
if ( defined( 'MW_CONFIG_CALLBACK' ) ) {
call_user_func( MW_CONFIG_CALLBACK );
} else {
$recursive = $this->options['changed']; // T52785
$updates = $this->getSecondaryDataUpdates( $recursive );
+ $triggeringUser = $this->options['triggeringuser'] ?? $this->user;
+ if ( !$triggeringUser instanceof User ) {
+ $triggeringUser = User::newFromIdentity( $triggeringUser );
+ }
foreach ( $updates as $update ) {
// TODO: make an $option field for the cause
- $update->setCause( 'edit-page', $this->user->getName() );
+ $update->setCause( 'edit-page', $triggeringUser->getName() );
if ( $update instanceof LinksUpdate ) {
$update->setRevision( $legacyRevision );
-
- if ( !empty( $this->options['triggeringuser'] ) ) {
- /** @var UserIdentity|User $triggeringUser */
- $triggeringUser = $this->options['triggeringuser'];
- if ( !$triggeringUser instanceof User ) {
- $triggeringUser = User::newFromIdentity( $triggeringUser );
- }
-
- $update->setTriggeringUser( $triggeringUser );
- }
+ $update->setTriggeringUser( $triggeringUser );
}
DeferredUpdates::addUpdate( $update );
}
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\ILoadBalancer;
-use Wikimedia\Rdbms\LoadBalancer;
/**
* @author Addshore
*/
class NameTableStore {
- /** @var LoadBalancer */
+ /** @var ILoadBalancer */
private $loadBalancer;
/** @var WANObjectCache */
if ( $searchResult === false ) {
$id = $this->store( $name );
if ( $id === null ) {
- // RACE: $name was already in the db, probably just inserted, so load from master
- // Use DBO_TRX to avoid missing inserts due to other threads or REPEATABLE-READs
- $table = $this->loadTable(
- $this->getDBConnection( DB_MASTER, LoadBalancer::CONN_TRX_AUTOCOMMIT )
- );
+ // RACE: $name was already in the db, probably just inserted, so load from master.
+ // Use DBO_TRX to avoid missing inserts due to other threads or REPEATABLE-READs.
+ // ...but not during unit tests, because we need the fake DB tables of the default
+ // connection.
+ $connFlags = defined( 'MW_PHPUNIT_TEST' ) ? 0 : ILoadBalancer::CONN_TRX_AUTOCOMMIT;
+ $table = $this->reloadMap( $connFlags );
+
$searchResult = array_search( $name, $table, true );
if ( $searchResult === false ) {
// Insert failed due to IGNORE flag, but DB_MASTER didn't give us the data
$this->logger->error( $m );
throw new NameTableAccessException( $m );
}
- $this->purgeWANCache(
- function () {
- $this->cache->reap( $this->getCacheKey(), INF );
- }
- );
+ } elseif ( isset( $table[$id] ) ) {
+ throw new NameTableAccessException(
+ "Expected unused ID from database insert for '$name' "
+ . " into '{$this->table}', but ID $id is already associated with"
+ . " the name '{$table[$id]}'! This may indicate database corruption!" );
} else {
$table[$id] = $name;
$searchResult = $id;
+
// As store returned an ID we know we inserted so delete from WAN cache
$this->purgeWANCache(
function () {
return $searchResult;
}
+ /**
+ * Reloads the name table from the master database, and purges the WAN cache entry.
+ *
+ * @note This should only be called in situations where the local cache has been detected
+ * to be out of sync with the database. There should be no reason to call this method
+ * from outside the NameTabelStore during normal operation. This method may however be
+ * useful in unit tests.
+ *
+ * @param int $connFlags ILoadBalancer::CONN_XXX flags. Optional.
+ *
+ * @return \string[] The freshly reloaded name map
+ */
+ public function reloadMap( $connFlags = 0 ) {
+ $this->tableCache = $this->loadTable(
+ $this->getDBConnection( DB_MASTER, $connFlags )
+ );
+ $this->purgeWANCache(
+ function () {
+ $this->cache->reap( $this->getCacheKey(), INF );
+ }
+ );
+
+ return $this->tableCache;
+ }
+
/**
* Get the id of the given name.
* If the name doesn't exist this will throw.
$this->slotsUpdate->modifyContent( $role, $content );
}
+ /**
+ * Set the new slot for the given slot role
+ *
+ * @param SlotRecord $slot
+ */
+ public function setSlot( SlotRecord $slot ) {
+ $this->slotsUpdate->modifySlot( $slot );
+ }
+
/**
* Explicitly inherit a slot from some earlier revision.
*
if ( !isset( $revisionRow['rev_id'] ) ) {
// only if auto-increment was used
$revisionRow['rev_id'] = intval( $dbw->insertId() );
+
+ if ( $dbw->getType() === 'mysql' ) {
+ // (T202032) MySQL until 8.0 and MariaDB until some version after 10.1.34 don't save the
+ // auto-increment value to disk, so on server restart it might reuse IDs from deleted
+ // revisions. We can fix that with an insert with an explicit rev_id value, if necessary.
+
+ $maxRevId = intval( $dbw->selectField( 'archive', 'MAX(ar_rev_id)', '', __METHOD__ ) );
+ $table = 'archive';
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
+ $maxRevId2 = intval( $dbw->selectField( 'slots', 'MAX(slot_revision_id)', '', __METHOD__ ) );
+ if ( $maxRevId2 >= $maxRevId ) {
+ $maxRevId = $maxRevId2;
+ $table = 'slots';
+ }
+ }
+
+ if ( $maxRevId >= $revisionRow['rev_id'] ) {
+ $this->logger->debug(
+ '__METHOD__: Inserted revision {revid} but {table} has revisions up to {maxrevid}.'
+ . ' Trying to fix it.',
+ [
+ 'revid' => $revisionRow['rev_id'],
+ 'table' => $table,
+ 'maxrevid' => $maxRevId,
+ ]
+ );
+
+ if ( !$dbw->lock( 'fix-for-T202032', __METHOD__ ) ) {
+ throw new MWException( 'Failed to get database lock for T202032' );
+ }
+ $fname = __METHOD__;
+ $dbw->onTransactionResolution( function ( $trigger, $dbw ) use ( $fname ) {
+ $dbw->unlock( 'fix-for-T202032', $fname );
+ } );
+
+ $dbw->delete( 'revision', [ 'rev_id' => $revisionRow['rev_id'] ], __METHOD__ );
+
+ // The locking here is mostly to make MySQL bypass the REPEATABLE-READ transaction
+ // isolation (weird MySQL "feature"). It does seem to block concurrent auto-incrementing
+ // inserts too, though, at least on MariaDB 10.1.29.
+ //
+ // Don't try to lock `revision` in this way, it'll deadlock if there are concurrent
+ // transactions in this code path thanks to the row lock from the original ->insert() above.
+ //
+ // And we have to use raw SQL to bypass the "aggregation used with a locking SELECT" warning
+ // that's for non-MySQL DBs.
+ $row1 = $dbw->query(
+ $dbw->selectSqlText( 'archive', [ 'v' => "MAX(ar_rev_id)" ], '', __METHOD__ ) . ' FOR UPDATE'
+ )->fetchObject();
+ if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
+ $row2 = $dbw->query(
+ $dbw->selectSqlText( 'slots', [ 'v' => "MAX(slot_revision_id)" ], '', __METHOD__ )
+ . ' FOR UPDATE'
+ )->fetchObject();
+ } else {
+ $row2 = null;
+ }
+ $maxRevId = max(
+ $maxRevId,
+ $row1 ? intval( $row1->v ) : 0,
+ $row2 ? intval( $row2->v ) : 0
+ );
+
+ // If we don't have SCHEMA_COMPAT_WRITE_NEW, all except the first of any concurrent
+ // transactions will throw a duplicate key error here. It doesn't seem worth trying
+ // to avoid that.
+ $revisionRow['rev_id'] = $maxRevId + 1;
+ $dbw->insert( 'revision', $revisionRow, __METHOD__ );
+ }
+ }
}
$commentCallback( $revisionRow['rev_id'] );
$slots = [];
foreach ( $res as $row ) {
+ // resolve role names and model names from in-memory cache, instead of joining.
+ $row->role_name = $this->slotRoleStore->getName( (int)$row->slot_role_id );
+ $row->model_name = $this->contentModelStore->getName( (int)$row->content_model );
+
$contentCallback = function ( SlotRecord $slot ) use ( $queryFlags, $row ) {
return $this->loadSlotContent( $slot, null, null, null, $queryFlags );
};
*
* @param array $options Any combination of the following strings
* - 'content': Join with the content table, and select content meta-data fields
+ * - 'model': Join with the content_models table, and select the model_name field.
+ * Only applicable if 'content' is also set.
+ * - 'role': Join with the slot_roles table, and select the role_name field
*
* @return array With three keys:
* - tables: (string[]) to include in the `$table` to `IDatabase->select()`
}
} else {
$ret['tables'][] = 'slots';
- $ret['tables'][] = 'slot_roles';
$ret['fields'] = array_merge( $ret['fields'], [
'slot_revision_id',
'slot_content_id',
'slot_origin',
- 'role_name'
+ 'slot_role_id',
] );
- $ret['joins']['slot_roles'] = [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ];
+
+ if ( in_array( 'role', $options, true ) ) {
+ // Use left join to attach role name, so we still find the revision row even
+ // if the role name is missing. This triggers a more obvious failure mode.
+ $ret['tables'][] = 'slot_roles';
+ $ret['joins']['slot_roles'] = [ 'LEFT JOIN', [ 'slot_role_id = role_id' ] ];
+ $ret['fields'][] = 'role_name';
+ }
if ( in_array( 'content', $options, true ) ) {
$ret['tables'][] = 'content';
- $ret['tables'][] = 'content_models';
$ret['fields'] = array_merge( $ret['fields'], [
'content_size',
'content_sha1',
'content_address',
- 'model_name'
+ 'content_model',
] );
$ret['joins']['content'] = [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ];
- $ret['joins']['content_models'] = [ 'INNER JOIN', [ 'content_model = model_id' ] ];
+
+ if ( in_array( 'model', $options, true ) ) {
+ // Use left join to attach model name, so we still find the revision row even
+ // if the model name is missing. This triggers a more obvious failure mode.
+ $ret['tables'][] = 'content_models';
+ $ret['joins']['content_models'] = [ 'LEFT JOIN', [ 'content_model = model_id' ] ];
+ $ret['fields'][] = 'model_name';
+ }
+
}
}
);
}
+ /**
+ * Is this a message which can contain raw HTML?
+ *
+ * @return bool
+ * @since 1.32
+ */
+ public function isRawHtmlMessage() {
+ global $wgRawHtmlMessages;
+
+ if ( $this->inNamespace( NS_MEDIAWIKI ) ) {
+ return false;
+ }
+ $message = lcfirst( $this->getRootText() );
+ return in_array( $message, $wgRawHtmlMessages, true );
+ }
+
/**
* Is this a talk page of some sort?
*
$error = [ 'sitejsonprotected', $action ];
} elseif ( $this->isSiteJsConfigPage() && !$user->isAllowed( 'editsitejs' ) ) {
$error = [ 'sitejsprotected', $action ];
+ } elseif ( $this->isRawHtmlMessage() ) {
+ // Raw HTML can be used to deploy CSS or JS so require rights for both.
+ if ( !$user->isAllowed( 'editsitejs' ) ) {
+ $error = [ 'sitejsprotected', $action ];
+ } elseif ( !$user->isAllowed( 'editsitecss' ) ) {
+ $error = [ 'sitecssprotected', $action ];
+ }
}
if ( $error ) {
# Protect css/json/js subpages of user pages
# XXX: this might be better using restrictions
- if ( $action != 'patrol' ) {
- if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
- if (
- $this->isUserCssConfigPage()
- && !$user->isAllowedAny( 'editmyusercss', 'editusercss' )
- ) {
- $errors[] = [ 'mycustomcssprotected', $action ];
- } elseif (
- $this->isUserJsonConfigPage()
- && !$user->isAllowedAny( 'editmyuserjson', 'edituserjson' )
- ) {
- $errors[] = [ 'mycustomjsonprotected', $action ];
- } elseif (
- $this->isUserJsConfigPage()
- && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' )
- ) {
- $errors[] = [ 'mycustomjsprotected', $action ];
- }
- } else {
+ if ( $action === 'patrol' ) {
+ return [];
+ }
+
+ if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
+ // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
+ if (
+ $this->isUserCssConfigPage()
+ && !$user->isAllowedAny( 'editmyusercss', 'editusercss' )
+ ) {
+ $errors[] = [ 'mycustomcssprotected', $action ];
+ } elseif (
+ $this->isUserJsonConfigPage()
+ && !$user->isAllowedAny( 'editmyuserjson', 'edituserjson' )
+ ) {
+ $errors[] = [ 'mycustomjsonprotected', $action ];
+ } elseif (
+ $this->isUserJsConfigPage()
+ && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' )
+ ) {
+ $errors[] = [ 'mycustomjsprotected', $action ];
+ }
+ } else {
+ // Users need editmyuser* to edit their own CSS/JSON/JS subpages, except for
+ // deletion/suppression which cannot be used for attacks and we want to avoid the
+ // situation where an unprivileged user can post abusive content on their subpages
+ // and only very highly privileged users could remove it.
+ if ( !in_array( $action, [ 'delete', 'deleterevision', 'suppressrevision' ], true ) ) {
if (
$this->isUserCssConfigPage()
&& !$user->isAllowed( 'editusercss' )
--- /dev/null
+<?php
+/**
+ * Temporary action for MCR undos
+ * @file
+ * @ingroup Actions
+ */
+
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\MutableRevisionRecord;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\SlotRecord;
+
+/**
+ * Temporary action for MCR undos
+ *
+ * This is intended to go away when real MCR support is added to EditPage and
+ * the standard undo-with-edit behavior can be implemented there instead.
+ *
+ * If this were going to be kept, we'd probably want to figure out a good way
+ * to reuse the same code for generating the headers, summary box, and buttons
+ * on EditPage and here, and to better share the diffing and preview logic
+ * between the two. But doing that now would require much of the rewriting of
+ * EditPage that we're trying to put off by doing this instead.
+ *
+ * @ingroup Actions
+ * @since 1.32
+ * @deprecated since 1.32
+ */
+class McrUndoAction extends FormAction {
+
+ private $undo = 0, $undoafter = 0, $cur = 0;
+
+ /** @param RevisionRecord|null */
+ private $curRev = null;
+
+ public function getName() {
+ return 'mcrundo';
+ }
+
+ public function getDescription() {
+ return '';
+ }
+
+ public function show() {
+ // Send a cookie so anons get talk message notifications
+ // (copied from SubmitAction)
+ MediaWiki\Session\SessionManager::getGlobalSession()->persist();
+
+ // Some stuff copied from EditAction
+ $this->useTransactionalTimeLimit();
+
+ $out = $this->getOutput();
+ $out->setRobotPolicy( 'noindex,nofollow' );
+ if ( $this->getContext()->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+ $out->addModuleStyles( [
+ 'mediawiki.ui.input',
+ 'mediawiki.ui.checkbox',
+ ] );
+ }
+
+ // IP warning headers copied from EditPage
+ // (should more be copied?)
+ if ( wfReadOnly() ) {
+ $out->wrapWikiMsg(
+ "<div id=\"mw-read-only-warning\">\n$1\n</div>",
+ [ 'readonlywarning', wfReadOnlyReason() ]
+ );
+ } elseif ( $this->context->getUser()->isAnon() ) {
+ if ( !$this->getRequest()->getCheck( 'wpPreview' ) ) {
+ $out->wrapWikiMsg(
+ "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
+ [ 'anoneditwarning',
+ // Log-in link
+ SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( [
+ 'returnto' => $this->getTitle()->getPrefixedDBkey()
+ ] ),
+ // Sign-up link
+ SpecialPage::getTitleFor( 'CreateAccount' )->getFullURL( [
+ 'returnto' => $this->getTitle()->getPrefixedDBkey()
+ ] )
+ ]
+ );
+ } else {
+ $out->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
+ 'anonpreviewwarning'
+ );
+ }
+ }
+
+ parent::show();
+ }
+
+ protected function checkCanExecute( User $user ) {
+ parent::checkCanExecute( $user );
+
+ $this->undoafter = $this->getRequest()->getInt( 'undoafter' );
+ $this->undo = $this->getRequest()->getInt( 'undo' );
+
+ if ( $this->undo == 0 || $this->undoafter == 0 ) {
+ throw new ErrorPageError( 'mcrundofailed', 'mcrundo-missingparam' );
+ }
+
+ $curRev = $this->page->getRevision();
+ if ( !$curRev ) {
+ throw new ErrorPageError( 'mcrundofailed', 'nopagetext' );
+ }
+ $this->curRev = $curRev->getRevisionRecord();
+ $this->cur = $this->getRequest()->getInt( 'cur', $this->curRev->getId() );
+
+ $revisionLookup = MediaWikiServices::getInstance()->getRevisionLookup();
+
+ $undoRev = $revisionLookup->getRevisionById( $this->undo );
+ $oldRev = $revisionLookup->getRevisionById( $this->undoafter );
+
+ if ( $undoRev === null || $oldRev === null ||
+ $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
+ $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
+ ) {
+ throw new ErrorPageError( 'mcrundofailed', 'undo-norev' );
+ }
+
+ return true;
+ }
+
+ /**
+ * @return MutableRevisionRecord
+ */
+ private function getNewRevision() {
+ $revisionLookup = MediaWikiServices::getInstance()->getRevisionLookup();
+
+ $undoRev = $revisionLookup->getRevisionById( $this->undo );
+ $oldRev = $revisionLookup->getRevisionById( $this->undoafter );
+ $curRev = $this->curRev;
+
+ $isLatest = $curRev->getId() === $undoRev->getId();
+
+ if ( $undoRev === null || $oldRev === null ||
+ $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ||
+ $oldRev->isDeleted( RevisionRecord::DELETED_TEXT )
+ ) {
+ throw new ErrorPageError( 'mcrundofailed', 'undo-norev' );
+ }
+
+ if ( $isLatest ) {
+ // Short cut! Undoing the current revision means we just restore the old.
+ return MutableRevisionRecord::newFromParentRevision( $oldRev );
+ }
+
+ $newRev = MutableRevisionRecord::newFromParentRevision( $curRev );
+
+ // Figure out the roles that need merging by first collecting all roles
+ // and then removing the ones that don't.
+ $rolesToMerge = array_unique( array_merge(
+ $oldRev->getSlotRoles(),
+ $undoRev->getSlotRoles(),
+ $curRev->getSlotRoles()
+ ) );
+
+ // Any roles with the same content in $oldRev and $undoRev can be
+ // inherited because undo won't change them.
+ $rolesToMerge = array_intersect(
+ $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent( $undoRev->getSlots() )
+ );
+ if ( !$rolesToMerge ) {
+ throw new ErrorPageError( 'mcrundofailed', 'undo-nochange' );
+ }
+
+ // Any roles with the same content in $oldRev and $curRev were already reverted
+ // and so can be inherited.
+ $rolesToMerge = array_intersect(
+ $rolesToMerge, $oldRev->getSlots()->getRolesWithDifferentContent( $curRev->getSlots() )
+ );
+ if ( !$rolesToMerge ) {
+ throw new ErrorPageError( 'mcrundofailed', 'undo-nochange' );
+ }
+
+ // Any roles with the same content in $undoRev and $curRev weren't
+ // changed since and so can be reverted to $oldRev.
+ $diffRoles = array_intersect(
+ $rolesToMerge, $undoRev->getSlots()->getRolesWithDifferentContent( $curRev->getSlots() )
+ );
+ foreach ( array_diff( $rolesToMerge, $diffRoles ) as $role ) {
+ if ( $oldRev->hasSlot( $role ) ) {
+ $newRev->inheritSlot( $oldRev->getSlot( $role, RevisionRecord::RAW ) );
+ } else {
+ $newRev->removeSlot( $role );
+ }
+ }
+ $rolesToMerge = $diffRoles;
+
+ // Any slot additions or removals not handled by the above checks can't be undone.
+ // There will be only one of the three revisions missing the slot:
+ // - !old means it was added in the undone revisions and modified after.
+ // Should it be removed entirely for the undo, or should the modified version be kept?
+ // - !undo means it was removed in the undone revisions and then readded with different content.
+ // Which content is should be kept, the old or the new?
+ // - !cur means it was changed in the undone revisions and then deleted after.
+ // Did someone delete vandalized content instead of undoing (meaning we should ideally restore
+ // it), or should it stay gone?
+ foreach ( $rolesToMerge as $role ) {
+ if ( !$oldRev->hasSlot( $role ) || !$undoRev->hasSlot( $role ) || !$curRev->hasSlot( $role ) ) {
+ throw new ErrorPageError( 'mcrundofailed', 'undo-failure' );
+ }
+ }
+
+ // Try to merge anything that's left.
+ foreach ( $rolesToMerge as $role ) {
+ $oldContent = $oldRev->getSlot( $role, RevisionRecord::RAW )->getContent();
+ $undoContent = $undoRev->getSlot( $role, RevisionRecord::RAW )->getContent();
+ $curContent = $curRev->getSlot( $role, RevisionRecord::RAW )->getContent();
+ $newContent = $undoContent->getContentHandler()
+ ->getUndoContent( $curContent, $undoContent, $oldContent, $isLatest );
+ if ( !$newContent ) {
+ throw new ErrorPageError( 'mcrundofailed', 'undo-failure' );
+ }
+ $newRev->setSlot( SlotRecord::newUnsaved( $role, $newContent ) );
+ }
+
+ return $newRev;
+ }
+
+ private function generateDiff() {
+ $newRev = $this->getNewRevision();
+ if ( $newRev->hasSameContent( $this->curRev ) ) {
+ throw new ErrorPageError( 'mcrundofailed', 'undo-nochange' );
+ }
+
+ $diffEngine = new DifferenceEngine( $this->context );
+ $diffEngine->setRevisions( $this->curRev, $newRev );
+
+ $oldtitle = $this->context->msg( 'currentrev' )->parse();
+ $newtitle = $this->context->msg( 'yourtext' )->parse();
+
+ if ( $this->getRequest()->getCheck( 'wpPreview' ) ) {
+ $diffEngine->renderNewRevision();
+ return '';
+ } else {
+ $diffText = $diffEngine->getDiff( $oldtitle, $newtitle );
+ $diffEngine->showDiffStyle();
+ return '<div id="wikiDiff">' . $diffText . '</div>';
+ }
+ }
+
+ public function onSubmit( $data ) {
+ global $wgUseRCPatrol;
+
+ if ( !$this->getRequest()->getCheck( 'wpSave' ) ) {
+ // Diff or preview
+ return false;
+ }
+
+ $updater = $this->page->getPage()->newPageUpdater( $this->context->getUser() );
+ $curRev = $updater->grabParentRevision();
+ if ( !$curRev ) {
+ throw new ErrorPageError( 'mcrundofailed', 'nopagetext' );
+ }
+
+ if ( $this->cur !== $curRev->getId() ) {
+ return Status::newFatal( 'mcrundo-changed' );
+ }
+
+ $newRev = $this->getNewRevision();
+ if ( !$newRev->hasSameContent( $curRev ) ) {
+ // Copy new slots into the PageUpdater, and remove any removed slots.
+ // TODO: This interface is awful, there should be a way to just pass $newRev.
+ // TODO: MCR: test this once we can store multiple slots
+ foreach ( $newRev->getSlots()->getSlots() as $slot ) {
+ $updater->setSlot( $slot );
+ }
+ foreach ( $curRev->getSlotRoles() as $role ) {
+ if ( !$newRev->hasSlot( $role ) ) {
+ $updater->removeSlot( $role );
+ }
+ }
+
+ $updater->setOriginalRevisionId( false );
+ $updater->setUndidRevisionId( $this->undo );
+
+ // TODO: Ugh.
+ if ( $wgUseRCPatrol && $this->getTitle()->userCan( 'autopatrol', $this->getUser() ) ) {
+ $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
+ }
+
+ $updater->saveRevision(
+ CommentStoreComment::newUnsavedComment( trim( $this->getRequest()->getVal( 'wpSummary' ) ) ),
+ EDIT_AUTOSUMMARY | EDIT_UPDATE
+ );
+
+ return $updater->getStatus();
+ }
+
+ return Status::newGood();
+ }
+
+ protected function usesOOUI() {
+ return true;
+ }
+
+ protected function getFormFields() {
+ $request = $this->getRequest();
+ $config = $this->context->getConfig();
+ $oldCommentSchema = $config->get( 'CommentTableSchemaMigrationStage' ) === MIGRATION_OLD;
+ $ret = [
+ 'diff' => [
+ 'type' => 'info',
+ 'vertical-label' => true,
+ 'raw' => true,
+ 'default' => function () {
+ return $this->generateDiff();
+ }
+ ],
+ 'summary' => [
+ 'type' => 'text',
+ 'id' => 'wpSummary',
+ 'name' => 'wpSummary',
+ 'cssclass' => 'mw-summary',
+ 'label-message' => 'summary',
+ 'maxlength' => $oldCommentSchema ? 200 : CommentStore::COMMENT_CHARACTER_LIMIT,
+ 'value' => $request->getVal( 'wpSummary', '' ),
+ 'size' => 60,
+ 'spellcheck' => 'true',
+ ],
+ 'summarypreview' => [
+ 'type' => 'info',
+ 'label-message' => 'summary-preview',
+ 'raw' => true,
+ ],
+ ];
+
+ if ( $request->getCheck( 'wpSummary' ) ) {
+ $ret['summarypreview']['default'] = Xml::tags( 'div', [ 'class' => 'mw-summary-preview' ],
+ Linker::commentBlock( trim( $request->getVal( 'wpSummary' ) ), $this->getTitle(), false )
+ );
+ } else {
+ unset( $ret['summarypreview'] );
+ }
+
+ return $ret;
+ }
+
+ protected function alterForm( HTMLForm $form ) {
+ $form->setWrapperLegendMsg( 'confirm-mcrundo-title' );
+
+ $labelAsPublish = $this->context->getConfig()->get( 'EditSubmitButtonLabelPublish' );
+
+ $form->setSubmitName( 'wpSave' );
+ $form->setSubmitTooltip( $labelAsPublish ? 'publish' : 'save' );
+ $form->setSubmitTextMsg( $labelAsPublish ? 'publishchanges' : 'savechanges' );
+ $form->showCancel( true );
+ $form->setCancelTarget( $this->getTitle() );
+ $form->addButton( [
+ 'name' => 'wpPreview',
+ 'value' => '1',
+ 'label-message' => 'showpreview',
+ 'attribs' => Linker::tooltipAndAccesskeyAttribs( 'preview' ),
+ ] );
+ $form->addButton( [
+ 'name' => 'wpDiff',
+ 'value' => '1',
+ 'label-message' => 'showdiff',
+ 'attribs' => Linker::tooltipAndAccesskeyAttribs( 'diff' ),
+ ] );
+
+ $form->addHiddenField( 'undo', $this->undo );
+ $form->addHiddenField( 'undoafter', $this->undoafter );
+ $form->addHiddenField( 'cur', $this->curRev->getId() );
+ }
+
+ public function onSuccess() {
+ $this->getOutput()->redirect( $this->getTitle()->getFullURL() );
+ }
+
+ protected function preText() {
+ return '<div style="clear:both"></div>';
+ }
+}
* @file
*/
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\MutableRevisionRecord;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\RevisionStore;
+
class ApiComparePages extends ApiBase {
- private $guessed = false, $guessedTitle, $guessedModel, $props;
+ /** @var RevisionStore */
+ private $revisionStore;
+
+ private $guessedTitle = false, $props;
+
+ public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
+ parent::__construct( $mainModule, $moduleName, $modulePrefix );
+ $this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
+ }
public function execute() {
$params = $this->extractRequestParams();
// Parameter validation
- $this->requireAtLeastOneParameter( $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext' );
- $this->requireAtLeastOneParameter( $params, 'totitle', 'toid', 'torev', 'totext', 'torelative' );
+ $this->requireAtLeastOneParameter(
+ $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext', 'fromslots'
+ );
+ $this->requireAtLeastOneParameter(
+ $params, 'totitle', 'toid', 'torev', 'totext', 'torelative', 'toslots'
+ );
$this->props = array_flip( $params['prop'] );
// Cache responses publicly by default. This may be overridden later.
$this->getMain()->setCacheMode( 'public' );
- // Get the 'from' Revision and Content
- list( $fromRev, $fromContent, $relRev ) = $this->getDiffContent( 'from', $params );
+ // Get the 'from' RevisionRecord
+ list( $fromRev, $fromRelRev, $fromValsRev ) = $this->getDiffRevision( 'from', $params );
- // Get the 'to' Revision and Content
+ // Get the 'to' RevisionRecord
if ( $params['torelative'] !== null ) {
- if ( !$relRev ) {
+ if ( !$fromRelRev ) {
$this->dieWithError( 'apierror-compare-relative-to-nothing' );
}
switch ( $params['torelative'] ) {
case 'prev':
// Swap 'from' and 'to'
- $toRev = $fromRev;
- $toContent = $fromContent;
- $fromRev = $relRev->getPrevious();
- $fromContent = $fromRev
- ? $fromRev->getContent( Revision::FOR_THIS_USER, $this->getUser() )
- : $toContent->getContentHandler()->makeEmptyContent();
- if ( !$fromContent ) {
- $this->dieWithError(
- [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent'
- );
- }
+ list( $toRev, $toRelRev2, $toValsRev ) = [ $fromRev, $fromRelRev, $fromValsRev ];
+ $fromRev = $this->revisionStore->getPreviousRevision( $fromRelRev );
+ $fromRelRev = $fromRev;
+ $fromValsRev = $fromRev;
break;
case 'next':
- $toRev = $relRev->getNext();
- $toContent = $toRev
- ? $toRev->getContent( Revision::FOR_THIS_USER, $this->getUser() )
- : $fromContent;
- if ( !$toContent ) {
- $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
- }
+ $toRev = $this->revisionStore->getNextRevision( $fromRelRev );
+ $toRelRev = $toRev;
+ $toValsRev = $toRev;
break;
case 'cur':
- $title = $relRev->getTitle();
- $id = $title->getLatestRevID();
- $toRev = $id ? Revision::newFromId( $id ) : null;
+ $title = $fromRelRev->getPageAsLinkTarget();
+ $toRev = $this->revisionStore->getRevisionByTitle( $title );
if ( !$toRev ) {
+ $title = Title::newFromLinkTarget( $title );
$this->dieWithError(
[ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ], 'nosuchrevid'
);
}
- $toContent = $toRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
- if ( !$toContent ) {
- $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
- }
+ $toRelRev = $toRev;
+ $toValsRev = $toRev;
break;
}
- $relRev2 = null;
} else {
- list( $toRev, $toContent, $relRev2 ) = $this->getDiffContent( 'to', $params );
+ list( $toRev, $toRelRev, $toValsRev ) = $this->getDiffRevision( 'to', $params );
}
- // Should never happen, but just in case...
- if ( !$fromContent || !$toContent ) {
+ // Handle missing from or to revisions
+ if ( !$fromRev || !$toRev ) {
$this->dieWithError( 'apierror-baddiff' );
}
- // Extract sections, if told to
- if ( isset( $params['fromsection'] ) ) {
- $fromContent = $fromContent->getSection( $params['fromsection'] );
- if ( !$fromContent ) {
- $this->dieWithError(
- [ 'apierror-compare-nosuchfromsection', wfEscapeWikiText( $params['fromsection'] ) ],
- 'nosuchfromsection'
- );
- }
+ // Handle revdel
+ if ( !$fromRev->audienceCan(
+ RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
+ ) ) {
+ $this->dieWithError( [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent' );
}
- if ( isset( $params['tosection'] ) ) {
- $toContent = $toContent->getSection( $params['tosection'] );
- if ( !$toContent ) {
- $this->dieWithError(
- [ 'apierror-compare-nosuchtosection', wfEscapeWikiText( $params['tosection'] ) ],
- 'nosuchtosection'
- );
- }
+ if ( !$toRev->audienceCan(
+ RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
+ ) ) {
+ $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
}
// Get the diff
$context = new DerivativeContext( $this->getContext() );
- if ( $relRev && $relRev->getTitle() ) {
- $context->setTitle( $relRev->getTitle() );
- } elseif ( $relRev2 && $relRev2->getTitle() ) {
- $context->setTitle( $relRev2->getTitle() );
+ if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) {
+ $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) );
+ } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) {
+ $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) );
} else {
- $this->guessTitleAndModel();
- if ( $this->guessedTitle ) {
- $context->setTitle( $this->guessedTitle );
+ $guessedTitle = $this->guessTitle();
+ if ( $guessedTitle ) {
+ $context->setTitle( $guessedTitle );
}
}
- $de = $fromContent->getContentHandler()->createDifferenceEngine(
- $context,
- $fromRev ? $fromRev->getId() : 0,
- $toRev ? $toRev->getId() : 0,
- /* $rcid = */ null,
- /* $refreshCache = */ false,
- /* $unhide = */ true
- );
- $de->setContent( $fromContent, $toContent );
- $difftext = $de->getDiffBody();
- if ( $difftext === false ) {
- $this->dieWithError( 'apierror-baddiff' );
+ $de = new DifferenceEngine( $context );
+ $de->setRevisions( $fromRev, $toRev );
+ if ( $params['slots'] === null ) {
+ $difftext = $de->getDiffBody();
+ if ( $difftext === false ) {
+ $this->dieWithError( 'apierror-baddiff' );
+ }
+ } else {
+ $difftext = [];
+ foreach ( $params['slots'] as $role ) {
+ $difftext[$role] = $de->getDiffBodyForRole( $role );
+ }
}
// Fill in the response
$vals = [];
- $this->setVals( $vals, 'from', $fromRev );
- $this->setVals( $vals, 'to', $toRev );
+ $this->setVals( $vals, 'from', $fromValsRev );
+ $this->setVals( $vals, 'to', $toValsRev );
if ( isset( $this->props['rel'] ) ) {
- if ( $fromRev ) {
- $rev = $fromRev->getPrevious();
+ if ( !$fromRev instanceof MutableRevisionRecord ) {
+ $rev = $this->revisionStore->getPreviousRevision( $fromRev );
if ( $rev ) {
$vals['prev'] = $rev->getId();
}
}
- if ( $toRev ) {
- $rev = $toRev->getNext();
+ if ( !$toRev instanceof MutableRevisionRecord ) {
+ $rev = $this->revisionStore->getNextRevision( $toRev );
if ( $rev ) {
$vals['next'] = $rev->getId();
}
}
if ( isset( $this->props['diffsize'] ) ) {
- $vals['diffsize'] = strlen( $difftext );
+ $vals['diffsize'] = 0;
+ foreach ( (array)$difftext as $text ) {
+ $vals['diffsize'] += strlen( $text );
+ }
}
if ( isset( $this->props['diff'] ) ) {
- ApiResult::setContentValue( $vals, 'body', $difftext );
+ if ( is_array( $difftext ) ) {
+ ApiResult::setArrayType( $difftext, 'kvp', 'diff' );
+ $vals['bodies'] = $difftext;
+ } else {
+ ApiResult::setContentValue( $vals, 'body', $difftext );
+ }
}
// Diffs can be really big and there's little point in having
}
/**
- * Guess an appropriate default Title and content model for this request
+ * Load a revision by ID
*
- * Fills in $this->guessedTitle based on the first of 'fromrev',
- * 'fromtitle', 'fromid', 'torev', 'totitle', and 'toid' that's present and
- * valid.
+ * Falls back to checking the archive table if appropriate.
+ *
+ * @param int $id
+ * @return RevisionRecord|null
+ */
+ private function getRevisionById( $id ) {
+ $rev = $this->revisionStore->getRevisionById( $id );
+ if ( !$rev && $this->getUser()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
+ // Try the 'archive' table
+ $arQuery = $this->revisionStore->getArchiveQueryInfo();
+ $row = $this->getDB()->selectRow(
+ $arQuery['tables'],
+ array_merge(
+ $arQuery['fields'],
+ [ 'ar_namespace', 'ar_title' ]
+ ),
+ [ 'ar_rev_id' => $id ],
+ __METHOD__,
+ [],
+ $arQuery['joins']
+ );
+ if ( $row ) {
+ $rev = $this->revisionStore->newRevisionFromArchiveRow( $row );
+ $rev->isArchive = true;
+ }
+ }
+ return $rev;
+ }
+
+ /**
+ * Guess an appropriate default Title for this request
*
- * Fills in $this->guessedModel based on the Revision or Title used to
- * determine $this->guessedTitle, or the 'fromcontentmodel' or
- * 'tocontentmodel' parameters if no title was guessed.
+ * @return Title|null
*/
- private function guessTitleAndModel() {
- if ( $this->guessed ) {
- return;
+ private function guessTitle() {
+ if ( $this->guessedTitle !== false ) {
+ return $this->guessedTitle;
}
- $this->guessed = true;
+ $this->guessedTitle = null;
$params = $this->extractRequestParams();
foreach ( [ 'from', 'to' ] as $prefix ) {
if ( $params["{$prefix}rev"] !== null ) {
- $revId = $params["{$prefix}rev"];
- $rev = Revision::newFromId( $revId );
- if ( !$rev ) {
- // Titles of deleted revisions aren't secret, per T51088
- $arQuery = Revision::getArchiveQueryInfo();
- $row = $this->getDB()->selectRow(
- $arQuery['tables'],
- array_merge(
- $arQuery['fields'],
- [ 'ar_namespace', 'ar_title' ]
- ),
- [ 'ar_rev_id' => $revId ],
- __METHOD__,
- [],
- $arQuery['joins']
- );
- if ( $row ) {
- $rev = Revision::newFromArchiveRow( $row );
- }
- }
+ $rev = $this->getRevisionById( $params["{$prefix}rev"] );
if ( $rev ) {
- $this->guessedTitle = $rev->getTitle();
- $this->guessedModel = $rev->getContentModel();
+ $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
break;
}
}
}
}
- if ( !$this->guessedModel ) {
- if ( $this->guessedTitle ) {
- $this->guessedModel = $this->guessedTitle->getContentModel();
- } elseif ( $params['fromcontentmodel'] !== null ) {
- $this->guessedModel = $params['fromcontentmodel'];
- } elseif ( $params['tocontentmodel'] !== null ) {
- $this->guessedModel = $params['tocontentmodel'];
+ return $this->guessedTitle;
+ }
+
+ /**
+ * Guess an appropriate default content model for this request
+ * @param string $role Slot for which to guess the model
+ * @return string|null Guessed content model
+ */
+ private function guessModel( $role ) {
+ $params = $this->extractRequestParams();
+
+ $title = null;
+ foreach ( [ 'from', 'to' ] as $prefix ) {
+ if ( $params["{$prefix}rev"] !== null ) {
+ $rev = $this->getRevisionById( $params["{$prefix}rev"] );
+ if ( $rev ) {
+ if ( $rev->hasSlot( $role ) ) {
+ return $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
+ }
+ }
+ }
+ }
+
+ $guessedTitle = $this->guessTitle();
+ if ( $guessedTitle && $role === 'main' ) {
+ // @todo: Use SlotRoleRegistry and do this for all slots
+ return $guessedTitle->getContentModel();
+ }
+
+ if ( isset( $params["fromcontentmodel-$role"] ) ) {
+ return $params["fromcontentmodel-$role"];
+ }
+ if ( isset( $params["tocontentmodel-$role"] ) ) {
+ return $params["tocontentmodel-$role"];
+ }
+
+ if ( $role === 'main' ) {
+ if ( isset( $params['fromcontentmodel'] ) ) {
+ return $params['fromcontentmodel'];
+ }
+ if ( isset( $params['tocontentmodel'] ) ) {
+ return $params['tocontentmodel'];
}
}
+
+ return null;
}
/**
- * Get the Revision and Content for one side of the diff
+ * Get the RevisionRecord for one side of the diff
*
- * This uses the appropriate set of 'rev', 'id', 'title', 'text', 'pst',
- * 'contentmodel', and 'contentformat' parameters to determine what content
+ * This uses the appropriate set of parameters to determine what content
* should be diffed.
*
* Returns three values:
- * - The revision used to retrieve the content, if any
- * - The content to be diffed
- * - The revision specified, if any, even if not used to retrieve the
- * Content
+ * - A RevisionRecord holding the content
+ * - The revision specified, if any, even if content was supplied
+ * - The revision to pass to setVals(), if any
*
* @param string $prefix 'from' or 'to'
* @param array $params
- * @return array [ Revision|null, Content, Revision|null ]
+ * @return array [ RevisionRecord|null, RevisionRecord|null, RevisionRecord|null ]
*/
- private function getDiffContent( $prefix, array $params ) {
+ private function getDiffRevision( $prefix, array $params ) {
+ // Back compat params
+ $this->requireMaxOneParameter( $params, "{$prefix}text", "{$prefix}slots" );
+ $this->requireMaxOneParameter( $params, "{$prefix}section", "{$prefix}slots" );
+ if ( $params["{$prefix}text"] !== null ) {
+ $params["{$prefix}slots"] = [ 'main' ];
+ $params["{$prefix}text-main"] = $params["{$prefix}text"];
+ $params["{$prefix}section-main"] = null;
+ $params["{$prefix}contentmodel-main"] = $params["{$prefix}contentmodel"];
+ $params["{$prefix}contentformat-main"] = $params["{$prefix}contentformat"];
+ }
+
$title = null;
$rev = null;
- $suppliedContent = $params["{$prefix}text"] !== null;
+ $suppliedContent = $params["{$prefix}slots"] !== null;
// Get the revision and title, if applicable
$revId = null;
}
}
if ( $revId !== null ) {
- $rev = Revision::newFromId( $revId );
- if ( !$rev && $this->getUser()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
- // Try the 'archive' table
- $arQuery = Revision::getArchiveQueryInfo();
- $row = $this->getDB()->selectRow(
- $arQuery['tables'],
- array_merge(
- $arQuery['fields'],
- [ 'ar_namespace', 'ar_title' ]
- ),
- [ 'ar_rev_id' => $revId ],
- __METHOD__,
- [],
- $arQuery['joins']
- );
- if ( $row ) {
- $rev = Revision::newFromArchiveRow( $row );
- $rev->isArchive = true;
- }
- }
+ $rev = $this->getRevisionById( $revId );
if ( !$rev ) {
$this->dieWithError( [ 'apierror-nosuchrevid', $revId ] );
}
- $title = $rev->getTitle();
+ $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
// If we don't have supplied content, return here. Otherwise,
// continue on below with the supplied content.
if ( !$suppliedContent ) {
- $content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
- if ( !$content ) {
- $this->dieWithError( [ 'apierror-missingcontent-revid', $revId ], 'missingcontent' );
+ $newRev = $rev;
+
+ // Deprecated 'fromsection'/'tosection'
+ if ( isset( $params["{$prefix}section"] ) ) {
+ $section = $params["{$prefix}section"];
+ $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
+ $content = $rev->getContent( 'main', RevisionRecord::FOR_THIS_USER, $this->getUser() );
+ if ( !$content ) {
+ $this->dieWithError(
+ [ 'apierror-missingcontent-revid-role', $rev->getId(), 'main' ], 'missingcontent'
+ );
+ }
+ $content = $content ? $content->getSection( $section ) : null;
+ if ( !$content ) {
+ $this->dieWithError(
+ [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
+ "nosuch{$prefix}section"
+ );
+ }
+ $newRev->setContent( 'main', $content );
}
- return [ $rev, $content, $rev ];
+
+ return [ $newRev, $rev, $rev ];
}
}
// Override $content based on supplied text
- $model = $params["{$prefix}contentmodel"];
- $format = $params["{$prefix}contentformat"];
-
- if ( !$model && $rev ) {
- $model = $rev->getContentModel();
- }
- if ( !$model && $title ) {
- $model = $title->getContentModel();
- }
- if ( !$model ) {
- $this->guessTitleAndModel();
- $model = $this->guessedModel;
- }
- if ( !$model ) {
- $model = CONTENT_MODEL_WIKITEXT;
- $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] );
- }
-
if ( !$title ) {
- $this->guessTitleAndModel();
- $title = $this->guessedTitle;
+ $title = $this->guessTitle();
}
-
- try {
- $content = ContentHandler::makeContent( $params["{$prefix}text"], $title, $model, $format );
- } catch ( MWContentSerializationException $ex ) {
- $this->dieWithException( $ex, [
- 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
+ if ( $rev ) {
+ $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
+ } else {
+ $newRev = $this->revisionStore->newMutableRevisionFromArray( [
+ 'title' => $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ )
] );
}
+ foreach ( $params["{$prefix}slots"] as $role ) {
+ $text = $params["{$prefix}text-{$role}"];
+ if ( $text === null ) {
+ $newRev->removeSlot( $role );
+ continue;
+ }
+
+ $model = $params["{$prefix}contentmodel-{$role}"];
+ $format = $params["{$prefix}contentformat-{$role}"];
- if ( $params["{$prefix}pst"] ) {
- if ( !$title ) {
- $this->dieWithError( 'apierror-compare-no-title' );
+ if ( !$model && $rev && $rev->hasSlot( $role ) ) {
+ $model = $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
+ }
+ if ( !$model && $title && $role === 'main' ) {
+ // @todo: Use SlotRoleRegistry and do this for all slots
+ $model = $title->getContentModel();
+ }
+ if ( !$model ) {
+ $model = $this->guessModel( $role );
+ }
+ if ( !$model ) {
+ $model = CONTENT_MODEL_WIKITEXT;
+ $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] );
+ }
+
+ try {
+ $content = ContentHandler::makeContent( $text, $title, $model, $format );
+ } catch ( MWContentSerializationException $ex ) {
+ $this->dieWithException( $ex, [
+ 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
+ ] );
+ }
+
+ if ( $params["{$prefix}pst"] ) {
+ if ( !$title ) {
+ $this->dieWithError( 'apierror-compare-no-title' );
+ }
+ $popts = ParserOptions::newFromContext( $this->getContext() );
+ $content = $content->preSaveTransform( $title, $this->getUser(), $popts );
+ }
+
+ $section = $params["{$prefix}section-{$role}"];
+ if ( $section !== null && $section !== '' ) {
+ if ( !$rev ) {
+ $this->dieWithError( "apierror-compare-no{$prefix}revision" );
+ }
+ $oldContent = $rev->getContent( $role, RevisionRecord::FOR_THIS_USER, $this->getUser() );
+ if ( !$oldContent ) {
+ $this->dieWithError(
+ [ 'apierror-missingcontent-revid-role', $rev->getId(), wfEscapeWikiText( $role ) ],
+ 'missingcontent'
+ );
+ }
+ if ( !$oldContent->getContentHandler()->supportsSections() ) {
+ $this->dieWithError( [ 'apierror-sectionsnotsupported', $content->getModel() ] );
+ }
+ try {
+ $content = $oldContent->replaceSection( $section, $content, '' );
+ } catch ( Exception $ex ) {
+ // Probably a content model mismatch.
+ $content = null;
+ }
+ if ( !$content ) {
+ $this->dieWithError( [ 'apierror-sectionreplacefailed' ] );
+ }
+ }
+
+ // Deprecated 'fromsection'/'tosection'
+ if ( $role === 'main' && isset( $params["{$prefix}section"] ) ) {
+ $section = $params["{$prefix}section"];
+ $content = $content->getSection( $section );
+ if ( !$content ) {
+ $this->dieWithError(
+ [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
+ "nosuch{$prefix}section"
+ );
+ }
}
- $popts = ParserOptions::newFromContext( $this->getContext() );
- $content = $content->preSaveTransform( $title, $this->getUser(), $popts );
- }
- return [ null, $content, $rev ];
+ $newRev->setContent( $role, $content );
+ }
+ return [ $newRev, $rev, null ];
}
/**
- * Set value fields from a Revision object
+ * Set value fields from a RevisionRecord object
+ *
* @param array &$vals Result array to set data into
* @param string $prefix 'from' or 'to'
- * @param Revision|null $rev
+ * @param RevisionRecord|null $rev
*/
private function setVals( &$vals, $prefix, $rev ) {
if ( $rev ) {
- $title = $rev->getTitle();
+ $title = $rev->getPageAsLinkTarget();
if ( isset( $this->props['ids'] ) ) {
$vals["{$prefix}id"] = $title->getArticleID();
$vals["{$prefix}revid"] = $rev->getId();
}
$anyHidden = false;
- if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
$vals["{$prefix}texthidden"] = true;
$anyHidden = true;
}
- if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
+ if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
$vals["{$prefix}userhidden"] = true;
$anyHidden = true;
}
- if ( isset( $this->props['user'] ) &&
- $rev->userCan( Revision::DELETED_USER, $this->getUser() )
- ) {
- $vals["{$prefix}user"] = $rev->getUserText( Revision::RAW );
- $vals["{$prefix}userid"] = $rev->getUser( Revision::RAW );
+ if ( isset( $this->props['user'] ) ) {
+ $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->getUser() );
+ if ( $user ) {
+ $vals["{$prefix}user"] = $user->getName();
+ $vals["{$prefix}userid"] = $user->getId();
+ }
}
- if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
+ if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
$vals["{$prefix}commenthidden"] = true;
$anyHidden = true;
}
- if ( $rev->userCan( Revision::DELETED_COMMENT, $this->getUser() ) ) {
- if ( isset( $this->props['comment'] ) ) {
- $vals["{$prefix}comment"] = $rev->getComment( Revision::RAW );
- }
- if ( isset( $this->props['parsedcomment'] ) ) {
+ if ( isset( $this->props['comment'] ) || isset( $this->props['parsedcomment'] ) ) {
+ $comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->getUser() );
+ if ( $comment !== null ) {
+ if ( isset( $this->props['comment'] ) ) {
+ $vals["{$prefix}comment"] = $comment->text;
+ }
$vals["{$prefix}parsedcomment"] = Linker::formatComment(
- $rev->getComment( Revision::RAW ),
- $rev->getTitle()
+ $comment->text, Title::newFromLinkTarget( $title )
);
}
}
if ( $anyHidden ) {
$this->getMain()->setCacheMode( 'private' );
- if ( $rev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
+ if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
$vals["{$prefix}suppressed"] = true;
}
}
}
public function getAllowedParams() {
+ $slotRoles = MediaWikiServices::getInstance()->getSlotRoleStore()->getMap();
+ if ( !in_array( 'main', $slotRoles, true ) ) {
+ $slotRoles[] = 'main';
+ }
+ sort( $slotRoles, SORT_STRING );
+
// Parameters for the 'from' and 'to' content
$fromToParams = [
'title' => null,
'rev' => [
ApiBase::PARAM_TYPE => 'integer'
],
- 'text' => [
- ApiBase::PARAM_TYPE => 'text'
+
+ 'slots' => [
+ ApiBase::PARAM_TYPE => $slotRoles,
+ ApiBase::PARAM_ISMULTI => true,
+ ],
+ 'text-{slot}' => [
+ ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
+ ApiBase::PARAM_TYPE => 'text',
+ ],
+ 'section-{slot}' => [
+ ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
+ ApiBase::PARAM_TYPE => 'string',
+ ],
+ 'contentformat-{slot}' => [
+ ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ],
+ 'contentmodel-{slot}' => [
+ ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
+ ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
],
- 'section' => null,
'pst' => false,
+
+ 'text' => [
+ ApiBase::PARAM_TYPE => 'text',
+ ApiBase::PARAM_DEPRECATED => true,
+ ],
'contentformat' => [
ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ApiBase::PARAM_DEPRECATED => true,
],
'contentmodel' => [
ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
- ]
+ ApiBase::PARAM_DEPRECATED => true,
+ ],
+ 'section' => [
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_DEPRECATED => true,
+ ],
];
$ret = [];
foreach ( $fromToParams as $k => $v ) {
+ if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
+ $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'fromslots';
+ }
$ret["from$k"] = $v;
}
foreach ( $fromToParams as $k => $v ) {
+ if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
+ $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'toslots';
+ }
$ret["to$k"] = $v;
}
ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
];
+ $ret['slots'] = [
+ ApiBase::PARAM_TYPE => $slotRoles,
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_ALL => true,
+ ];
+
return $ret;
}
MediaWikiServices::getInstance()->getStatsdDataFactory()->timing(
'api.' . $this->mModule->getModuleName() . '.executeTiming', 1000 * $runTime
);
- } catch ( Exception $e ) {
+ } catch ( Exception $e ) { // @todo Remove this block when HHVM is no longer supported
+ $this->handleException( $e );
+ $this->logRequest( microtime( true ) - $t, $e );
+ $isError = true;
+ } catch ( Throwable $e ) {
$this->handleException( $e );
$this->logRequest( microtime( true ) - $t, $e );
$isError = true;
* Handle an exception as an API response
*
* @since 1.23
- * @param Exception $e
+ * @param Exception|Throwable $e
*/
- protected function handleException( Exception $e ) {
+ protected function handleException( $e ) {
// T65145: Rollback any open database transactions
if ( !( $e instanceof ApiUsageException || $e instanceof UsageException ) ) {
// UsageExceptions are intentional, so don't rollback if that's the case
foreach ( $ex->getStatusValue()->getErrors() as $error ) {
try {
$this->mPrinter->addWarning( $error );
- } catch ( Exception $ex2 ) {
+ } catch ( Exception $ex2 ) { // @todo Remove this block when HHVM is no longer supported
+ // WTF?
+ $this->addWarning( $error );
+ } catch ( Throwable $ex2 ) {
// WTF?
$this->addWarning( $error );
}
* friendly to clients. If it fails, it will rethrow the exception.
*
* @since 1.23
- * @param Exception $e
- * @throws Exception
+ * @param Exception|Throwable $e
+ * @throws Exception|Throwable
*/
- public static function handleApiBeforeMainException( Exception $e ) {
+ public static function handleApiBeforeMainException( $e ) {
ob_start();
try {
$main = new self( RequestContext::getMain(), false );
$main->handleException( $e );
$main->logRequest( 0, $e );
- } catch ( Exception $e2 ) {
+ } catch ( Exception $e2 ) { // @todo Remove this block when HHVM is no longer supported
+ // Nope, even that didn't work. Punt.
+ throw $e;
+ } catch ( Throwable $e2 ) {
// Nope, even that didn't work. Punt.
throw $e;
}
* text around the exception's (presumably English) message as a single
* error (no warnings).
*
- * @param Exception $e
+ * @param Exception|Throwable $e
* @param string $type 'error' or 'warning'
* @return ApiMessage[]
* @since 1.27
/**
* Replace the result data with the information about an exception.
- * @param Exception $e
+ * @param Exception|Throwable $e
* @return string[] Error codes
*/
protected function substituteResultWithError( $e ) {
/**
* Log the preceding request
* @param float $time Time in seconds
- * @param Exception|null $e Exception caught while processing the request
+ * @param Exception|Throwable|null $e Exception caught while processing the request
*/
protected function logRequest( $time, $e = null ) {
$request = $this->getRequest();
protected function getExamplesMessages() {
return [
'action=query&prop=deletedrevisions&titles=Main%20Page|Talk:Main%20Page&' .
- 'drvprop=user|comment|content'
+ 'drvslots=*&drvprop=user|comment|content'
=> 'apihelp-query+deletedrevisions-example-titles',
'action=query&prop=deletedrevisions&revids=123456'
=> 'apihelp-query+deletedrevisions-example-revids',
protected function getExamplesMessages() {
return [
'action=query&prop=revisions&titles=API|Main%20Page&' .
- 'rvprop=timestamp|user|comment|content'
+ 'rvslots=*&rvprop=timestamp|user|comment|content'
=> 'apihelp-query+revisions-example-content',
'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
'rvprop=timestamp|user|comment'
"apihelp-compare-param-fromtitle": "العنوان الأول للمقارنة.",
"apihelp-compare-param-fromid": "رقم الصفحة الأول للمقارنة.",
"apihelp-compare-param-fromrev": "أول مراجعة للمقارنة.",
- "apihelp-compare-param-fromtext": "استخدم هذا النص بدلا من محتوى المراجعة المحدد بواسطة <var>fromtitle</var>، <var>fromid</var> أو <var>fromrev</var>.",
- "apihelp-compare-param-fromsection": "استخدم فقط القسم المحدد في المحتوى 'من' المحدد.",
"apihelp-compare-param-frompst": "قم بإجراء تحويل ما قبل الحفظ على <var>fromtext</var>.",
+ "apihelp-compare-param-fromtext": "استخدم هذا النص بدلا من محتوى المراجعة المحدد بواسطة <var>fromtitle</var>، <var>fromid</var> أو <var>fromrev</var>.",
"apihelp-compare-param-fromcontentmodel": "نموذج محتوى <var>fromtext</var>، إذا لم يتم توفيره، فسيتم تخمينه استنادا إلى الوسائط الأخرى.",
"apihelp-compare-param-fromcontentformat": "تنسيق محتوى تسلسل <var>fromtext</var>.",
+ "apihelp-compare-param-fromsection": "استخدم فقط القسم المحدد في المحتوى 'من' المحدد.",
"apihelp-compare-param-totitle": "العنوان الثاني للمقارنة.",
"apihelp-compare-param-toid": "رقم الصفحة الثاني للمقارنة.",
"apihelp-compare-param-torev": "المراجعة الثانية للمقارنة.",
"apihelp-compare-param-torelative": "استخدم مراجعة متعلقة بالمراجعة المحددة من <var>fromtitle</var> أو <var>fromid</var> أو <var>fromrev</var>، سيتم تجاهل جميع خيارات 'إلى' الأخرى.",
- "apihelp-compare-param-totext": "استخدم هذا النص بدلا من محتوى المراجعة المحدد بواسطة <var>totitle</var> أو <var>toid</var> أو <var>torev</var>.",
- "apihelp-compare-param-tosection": "استخدم فقط القسم المحدد في المحتوى 'إلى' المحدد.",
"apihelp-compare-param-topst": "قم بإجراء تحويل ما قبل الحفظ على <var>totext</var>.",
+ "apihelp-compare-param-totext": "استخدم هذا النص بدلا من محتوى المراجعة المحدد بواسطة <var>totitle</var> أو <var>toid</var> أو <var>torev</var>.",
"apihelp-compare-param-tocontentmodel": "نموذج محتوى <var>totext</var>، إذا لم يتم توفيره، فسيتم تخمينه استنادا إلى الوسائط الأخرى.",
"apihelp-compare-param-tocontentformat": "تنسيق محتوى تسلسل <var>totext</var>.",
+ "apihelp-compare-param-tosection": "استخدم فقط القسم المحدد في المحتوى 'إلى' المحدد.",
"apihelp-compare-param-prop": "أية قطعة من المعلومات للحصول عليها.",
"apihelp-compare-paramvalue-prop-diff": "HTML الفرق.",
"apihelp-compare-paramvalue-prop-diffsize": "حجم HTML الفرق، بالبايت.",
"apihelp-query+protectedtitles-paramvalue-prop-level": "Ergänzt den Schutzstatus.",
"apihelp-query+protectedtitles-example-simple": "Listet geschützte Titel auf.",
"apihelp-query+querypage-param-limit": "Anzahl der zurückzugebenden Ergebnisse.",
+ "apihelp-query+random-summary": "Ruft einen Satz an zufälligen Seiten ab.",
"apihelp-query+recentchanges-summary": "Listet die letzten Änderungen auf.",
"apihelp-query+recentchanges-param-user": "Listet nur Änderungen von diesem Benutzer auf.",
"apihelp-query+recentchanges-param-excludeuser": "Listet keine Änderungen von diesem Benutzer auf.",
"apierror-badparameter": "Ungültiger Wert für den Parameter <var>$1</var>.",
"apierror-badquery": "Ungültige Abfrage.",
"apierror-cannot-async-upload-file": "Die Parameter <var>async</var> und <var>file</var> können nicht kombiniert werden. Falls du eine asynchrone Verarbeitung deiner hochgeladenen Datei wünschst, lade sie zuerst mithilfe des Parameters <var>stash</var> auf den Speicher hoch. Veröffentliche anschließend die gespeicherte Datei asynchron mithilfe <var>filekey</var> und <var>async</var>.",
+ "apierror-compare-nofromrevision": "Keine Version „from“. <var>fromrev</var>, <var>fromtitle</var> oder <var>fromid</var> angeben.",
+ "apierror-compare-notorevision": "Keine Version „to“. <var>torev</var>, <var>totitle</var> oder <var>toid</var> angeben.",
"apierror-emptypage": "Das Erstellen neuer leerer Seiten ist nicht erlaubt.",
"apierror-filedoesnotexist": "Die Datei ist nicht vorhanden.",
"apierror-import-unknownerror": "Unbekannter Fehler beim Importieren: $1.",
"apierror-invaliduserid": "Die Benutzerkennung <var>$1</var> ist nicht gültig.",
"apierror-maxbytes": "Der Parameter <var>$1</var> kann nicht länger sein als {{PLURAL:$2|ein Byte|$2 Bytes}}",
"apierror-maxchars": "Der Parameter <var>$1</var> kann nicht länger sein als {{PLURAL:$2|ein|$2}} Zeichen",
+ "apierror-missingcontent-revid-role": "Fehlender Inhalt für die Versionskennung $1 für die Rolle $2.",
"apierror-nosuchsection": "Es gibt keinen Abschnitt $1.",
"apierror-nosuchuserid": "Es gibt keinen Benutzer mit der Kennung $1.",
"apierror-offline": "Aufgrund von Problemen bei der Netzwerkverbindung kannst du nicht weitermachen. Stelle sicher, dass du eine funktionierende Internetverbindung hast und versuche es erneut.",
"apihelp-compare-param-fromtitle": "First title to compare.",
"apihelp-compare-param-fromid": "First page ID to compare.",
"apihelp-compare-param-fromrev": "First revision to compare.",
- "apihelp-compare-param-fromtext": "Use this text instead of the content of the revision specified by <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var>.",
+ "apihelp-compare-param-frompst": "Do a pre-save transform on <var>fromtext-{slot}</var>.",
+ "apihelp-compare-param-fromslots": "Override content of the revision specified by <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var>.\n\nThis parameter specifies the slots that are to be modified. Use <var>fromtext-{slot}</var>, <var>fromcontentmodel-{slot}</var>, and <var>fromcontentformat-{slot}</var> to specify content for each slot.",
+ "apihelp-compare-param-fromtext-{slot}": "Text of the specified slot. If omitted, the slot is removed from the revision.",
+ "apihelp-compare-param-fromsection-{slot}": "When <var>fromtext-{slot}</var> is the content of a single section, this is the section number. It will be merged into the revision specified by <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var> as if for a section edit.",
+ "apihelp-compare-param-fromcontentmodel-{slot}": "Content model of <var>fromtext-{slot}</var>. If not supplied, it will be guessed based on the other parameters.",
+ "apihelp-compare-param-fromcontentformat-{slot}": "Content serialization format of <var>fromtext-{slot}</var>.",
+ "apihelp-compare-param-fromtext": "Specify <kbd>fromslots=main</kbd> and use <var>fromtext-main</var> instead.",
+ "apihelp-compare-param-fromcontentmodel": "Specify <kbd>fromslots=main</kbd> and use <var>fromcontentmodel-main</var> instead.",
+ "apihelp-compare-param-fromcontentformat": "Specify <kbd>fromslots=main</kbd> and use <var>fromcontentformat-main</var> instead.",
"apihelp-compare-param-fromsection": "Only use the specified section of the specified 'from' content.",
- "apihelp-compare-param-frompst": "Do a pre-save transform on <var>fromtext</var>.",
- "apihelp-compare-param-fromcontentmodel": "Content model of <var>fromtext</var>. If not supplied, it will be guessed based on the other parameters.",
- "apihelp-compare-param-fromcontentformat": "Content serialization format of <var>fromtext</var>.",
"apihelp-compare-param-totitle": "Second title to compare.",
"apihelp-compare-param-toid": "Second page ID to compare.",
"apihelp-compare-param-torev": "Second revision to compare.",
"apihelp-compare-param-torelative": "Use a revision relative to the revision determined from <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var>. All of the other 'to' options will be ignored.",
- "apihelp-compare-param-totext": "Use this text instead of the content of the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var>.",
- "apihelp-compare-param-tosection": "Only use the specified section of the specified 'to' content.",
"apihelp-compare-param-topst": "Do a pre-save transform on <var>totext</var>.",
- "apihelp-compare-param-tocontentmodel": "Content model of <var>totext</var>. If not supplied, it will be guessed based on the other parameters.",
- "apihelp-compare-param-tocontentformat": "Content serialization format of <var>totext</var>.",
+ "apihelp-compare-param-toslots": "Override content of the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var>.\n\nThis parameter specifies the slots that are to be modified. Use <var>totext-{slot}</var>, <var>tocontentmodel-{slot}</var>, and <var>tocontentformat-{slot}</var> to specify content for each slot.",
+ "apihelp-compare-param-totext-{slot}": "Text of the specified slot. If omitted, the slot is removed from the revision.",
+ "apihelp-compare-param-tosection-{slot}": "When <var>totext-{slot}</var> is the content of a single section, this is the section number. It will be merged into the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var> as if for a section edit.",
+ "apihelp-compare-param-tocontentmodel-{slot}": "Content model of <var>totext-{slot}</var>. If not supplied, it will be guessed based on the other parameters.",
+ "apihelp-compare-param-tocontentformat-{slot}": "Content serialization format of <var>totext-{slot}</var>.",
+ "apihelp-compare-param-totext": "Specify <kbd>toslots=main</kbd> and use <var>totext-main</var> instead.",
+ "apihelp-compare-param-tocontentmodel": "Specify <kbd>toslots=main</kbd> and use <var>tocontentmodel-main</var> instead.",
+ "apihelp-compare-param-tocontentformat": "Specify <kbd>toslots=main</kbd> and use <var>tocontentformat-main</var> instead.",
+ "apihelp-compare-param-tosection": "Only use the specified section of the specified 'to' content.",
"apihelp-compare-param-prop": "Which pieces of information to get.",
"apihelp-compare-paramvalue-prop-diff": "The diff HTML.",
"apihelp-compare-paramvalue-prop-diffsize": "The size of the diff HTML, in bytes.",
"apihelp-compare-paramvalue-prop-comment": "The comment on the 'from' and 'to' revisions.",
"apihelp-compare-paramvalue-prop-parsedcomment": "The parsed comment on the 'from' and 'to' revisions.",
"apihelp-compare-paramvalue-prop-size": "The size of the 'from' and 'to' revisions.",
+ "apihelp-compare-param-slots": "Return individual diffs for these slots, rather than one combined diff for all slots.",
"apihelp-compare-example-1": "Create a diff between revision 1 and 2.",
"apihelp-createaccount-summary": "Create a new user account.",
"apierror-compare-no-title": "Cannot pre-save transform without a title. Try specifying <var>fromtitle</var> or <var>totitle</var>.",
"apierror-compare-nosuchfromsection": "There is no section $1 in the 'from' content.",
"apierror-compare-nosuchtosection": "There is no section $1 in the 'to' content.",
+ "apierror-compare-nofromrevision": "No 'from' revision. Specify <var>fromrev</var>, <var>fromtitle</var>, or <var>fromid</var>.",
+ "apierror-compare-notorevision": "No 'to' revision. Specify <var>torev</var>, <var>totitle</var>, or <var>toid</var>.",
"apierror-compare-relative-to-nothing": "No 'from' revision for <var>torelative</var> to be relative to.",
"apierror-contentserializationexception": "Content serialization failed: $1",
"apierror-contenttoobig": "The content you supplied exceeds the article size limit of $1 {{PLURAL:$1|kilobyte|kilobytes}}.",
"apierror-mimesearchdisabled": "MIME search is disabled in Miser Mode.",
"apierror-missingcontent-pageid": "Missing content for page ID $1.",
"apierror-missingcontent-revid": "Missing content for revision ID $1.",
+ "apierror-missingcontent-revid-role": "Missing content for revision ID $1 for role $2.",
"apierror-missingparam-at-least-one-of": "{{PLURAL:$2|The parameter|At least one of the parameters}} $1 is required.",
"apierror-missingparam-one-of": "{{PLURAL:$2|The parameter|One of the parameters}} $1 is required.",
"apierror-missingparam": "The <var>$1</var> parameter must be set.",
"apihelp-compare-param-fromtitle": "Premier titre à comparer.",
"apihelp-compare-param-fromid": "ID de la première page à comparer.",
"apihelp-compare-param-fromrev": "Première révision à comparer.",
- "apihelp-compare-param-fromtext": "Utiliser ce texte au lieu du contenu de la révision spécifié par <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>.",
+ "apihelp-compare-param-frompst": "Faire une transformation avant enregistrement sur <var>fromtext-{slot}</var>.",
+ "apihelp-compare-param-fromslots": "Substituer le contenu de la révision spécifiée par <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>.\n\nCe paramètre spécifie les intervalles à modifier. Utilisez <var>fromtext-{slot}</var>, <var>fromcontentmodel-{slot}</var>, et <var>fromcontentformat-{slot}</var> pour spécifier le contenu de chaque intervalle.",
+ "apihelp-compare-param-fromtext-{slot}": "Texte de l'intervalle spécifié. Si absent, l'intervalle est supprimé de la révision.",
+ "apihelp-compare-param-fromsection-{slot}": "Si <var>fromtext-{slot}</var> est le contenu d'une seule section, c'est le numéro de la section. Il sera fusionné dans la révision spécifiée par <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var> comme pour les modifications de section.",
+ "apihelp-compare-param-fromcontentmodel-{slot}": "Modèle de contenu de <var>fromtext-{slot}</var>. Si non fourni, il sera déduit en fonction de la valeur des autres paramètres.",
+ "apihelp-compare-param-fromcontentformat-{slot}": "Format de sérialisation de contenu de <var>fromtext-{slot}</var>.",
+ "apihelp-compare-param-fromtext": "Spécifiez <kbd>fromslots=main</kbd> et utilisez <var>fromtext-main</var> à la place.",
+ "apihelp-compare-param-fromcontentmodel": "Spécifiez <kbd>fromslots=main</kbd> et utilisez <var>fromcontentmodel-main</var> à la place.",
+ "apihelp-compare-param-fromcontentformat": "Spécifiez <kbd>fromslots=main</kbd> et utilisez <var>fromcontentformat-main</var> à la place.",
"apihelp-compare-param-fromsection": "N'utiliser que la section spécifiée du contenu 'from'.",
- "apihelp-compare-param-frompst": "Faire une transformation avant enregistrement sur <var>fromtext</var>.",
- "apihelp-compare-param-fromcontentmodel": "Modèle de contenu de <var>fromtext</var>. Si non fourni, il sera déduit d’après les autres paramètres.",
- "apihelp-compare-param-fromcontentformat": "Sérialisation du contenu de <var>fromtext</var>.",
"apihelp-compare-param-totitle": "Second titre à comparer.",
"apihelp-compare-param-toid": "ID de la seconde page à comparer.",
"apihelp-compare-param-torev": "Seconde révision à comparer.",
"apihelp-compare-param-torelative": "Utiliser une révision relative à la révision déterminée de <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>. Toutes les autres options 'to' seront ignorées.",
- "apihelp-compare-param-totext": "Utiliser ce texte au lieu du contenu de la révision spécifié par <var>totitle</var>, <var>toid</var> ou <var>torev</var>.",
- "apihelp-compare-param-tosection": "N'utiliser que la section spécifiée du contenu 'to'.",
"apihelp-compare-param-topst": "Faire une transformation avant enregistrement sur <var>totext</var>.",
- "apihelp-compare-param-tocontentmodel": "Modèle de contenu de <var>totext</var>. Si non fourni, il sera deviné d’après les autres paramètres.",
- "apihelp-compare-param-tocontentformat": "Format de sérialisation du contenu de <var>totext</var>.",
+ "apihelp-compare-param-toslots": "Substitue le contenu de la révision spécifiée par <var>totitle</var>, <var>toid</var> ou <var>torev</var>.\n\nCe paramètre spécifie les intervalles qui vont être modifiés. Utilisez <var>totext-{slot}</var>, <var>tocontentmodel-{slot}</var>, et <var>tocontentformat-{slot}</var> pour spécifier le contenue de chaque intervalle.",
+ "apihelp-compare-param-totext-{slot}": "Texte de la relation spécifiée. Si absent, le lien est supprimé de la révision.",
+ "apihelp-compare-param-tosection-{slot}": "Si <var>totext-{slot}</var> est le contenu d'une seule section, c'est le numéro de la section. Il sera fusionné dans la révision spécifiée par <var>totitle</var>, <var>toid</var> ou <var>torev</var> comme pour les modifications de section.",
+ "apihelp-compare-param-tocontentmodel-{slot}": "Modèle de contenu de <var>totext-{slot}</var>. Si non fourni, il sera déduit en fonction de la valeur des autres paramètres.",
+ "apihelp-compare-param-tocontentformat-{slot}": "Format de sérialisation du contenu de <var>totext-{slot}</var>.",
+ "apihelp-compare-param-totext": "Spécifiez <kbd>toslots=main</kbd> et utilisez <var>totext-main</var> à la place.",
+ "apihelp-compare-param-tocontentmodel": "Spécifiez <kbd>toslots=main</kbd> et utilisez <var>tocontentmodel-main</var> à la place.",
+ "apihelp-compare-param-tocontentformat": "Spécifiez <kbd>toslots=main</kbd> et utilisez <var>tocontentformat-main</var> à la place.",
+ "apihelp-compare-param-tosection": "N'utiliser que la section spécifiée du contenu 'to'.",
"apihelp-compare-param-prop": "Quelles informations obtenir.",
"apihelp-compare-paramvalue-prop-diff": "Le diff HTML.",
"apihelp-compare-paramvalue-prop-diffsize": "La taille du diff HTML en octets.",
"apihelp-compare-paramvalue-prop-comment": "Le commentaire des révisions 'depuis' et 'vers'.",
"apihelp-compare-paramvalue-prop-parsedcomment": "Le commentaire analysé des révisions 'depuis' et 'vers'.",
"apihelp-compare-paramvalue-prop-size": "La taille des révisions 'depuis' et 'vers'.",
+ "apihelp-compare-param-slots": "Retourne les diffs individuels pour ces intervalles, plutôt qu'un diff combiné pour tous les intervalles.",
"apihelp-compare-example-1": "Créer une différence entre les révisions 1 et 2",
"apihelp-createaccount-summary": "Créer un nouveau compte utilisateur.",
"apihelp-createaccount-param-preservestate": "Si <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> a retourné true pour <samp>hasprimarypreservedstate</samp>, les demandes marquées comme <samp>primary-required</samp> doivent être omises. Si elle a retourné une valeur non vide pour <samp>preservedusername</samp>, ce nom d'utilisateur doit être utilisé pour le paramètre <var>username</var>.",
"apierror-compare-no-title": "Impossible de faire une transformation avant enregistrement sans titre. Essayez de spécifier <var>fromtitle</var> ou <var>totitle</var>.",
"apierror-compare-nosuchfromsection": "Il n'y a pas de section $1 dans le contenu 'from'.",
"apierror-compare-nosuchtosection": "Il n'y a pas de section $1 dans le contenu 'to'.",
+ "apierror-compare-nofromrevision": "Aucune révision 'from'. Spécifiez <var>fromrev</var>, <var>fromtitle</var>, ou <var>fromid</var>.",
+ "apierror-compare-notorevision": "Aucune révision 'to'. Spécifiez <var>torev</var>, <var>totitle</var>, ou <var>toid</var>.",
"apierror-compare-relative-to-nothing": "Pas de révision 'depuis' pour <var>torelative</var> à laquelle se rapporter.",
"apierror-contentserializationexception": "Échec de sérialisation du contenu : $1",
"apierror-contenttoobig": "Le contenu que vous avez fourni dépasse la limite de taille d’un article, qui est de $1 {{PLURAL:$1|kilooctet|kilooctets}}.",
"apierror-mimesearchdisabled": "La recherche MIME est désactivée en mode Misère.",
"apierror-missingcontent-pageid": "Contenu manquant pour la page d’ID $1.",
"apierror-missingcontent-revid": "Contenu de la révision d’ID $1 manquant.",
+ "apierror-missingcontent-revid-role": "Contenu absent pour l'ID de révision $1 pour le rôle $2.",
"apierror-missingparam-at-least-one-of": "{{PLURAL:$2|Le paramètre|Au moins un des paramètres}} $1 est obligatoire.",
"apierror-missingparam-one-of": "{{PLURAL:$2|Le paramètre|Un des paramètres}} $1 est obligatoire.",
"apierror-missingparam": "Le paramètre <var>$1</var> doit être défini.",
"apihelp-compare-param-fromtitle": "כותרת ראשונה להשוואה.",
"apihelp-compare-param-fromid": "מס׳ זיהוי של הדף הראשון להשוואה.",
"apihelp-compare-param-fromrev": "גרסה ראשונה להשוואה.",
- "apihelp-compare-param-fromtext": "להשתמש בטקסט הזה במקום תוכן הגרסה שהוגדרה על־ידי <var dir=\"ltr\">fromtitle</var>, <var dir=\"ltr\">fromid</var> או <var dir=\"ltr\">fromrev</var>.",
- "apihelp-compare-param-fromsection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'from'.",
"apihelp-compare-param-frompst": "לעשות התמרה לפני שמירה ב־<var>fromtext</var>.",
+ "apihelp-compare-param-fromtext": "להשתמש בטקסט הזה במקום תוכן הגרסה שהוגדרה על־ידי <var dir=\"ltr\">fromtitle</var>, <var dir=\"ltr\">fromid</var> או <var dir=\"ltr\">fromrev</var>.",
"apihelp-compare-param-fromcontentmodel": "מודל התוכן של <var>fromtext</var>. אם זה לא סופק, ייעשה ניחוש על סמך פרמטרים אחרים.",
"apihelp-compare-param-fromcontentformat": "תסדיר הסדרת תוכן של <var>fromtext</var>.",
+ "apihelp-compare-param-fromsection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'from'.",
"apihelp-compare-param-totitle": "כותרת שנייה להשוואה.",
"apihelp-compare-param-toid": "מס׳ מזהה של הדף השני להשוואה.",
"apihelp-compare-param-torev": "גרסה שנייה להשוואה.",
"apihelp-compare-param-torelative": "להשתמש בגרסה יחסית לגרסה שהוסקה מ<var dir=\"ltr\">fromtitle</var>, <var dir=\"ltr\">fromid</var> או <var dir=\"ltr\">fromrev</var>. לכל אפשריות ה־\"to\" האחרות לא תהיה השפעה.",
- "apihelp-compare-param-totext": "להשתמש בטקסט הזה במקום התוכן של הגרסה שהוגדר ב־<var dir=\"ltr\">totitle</var>, <var dir=\"ltr\">toid</var> or <var dir=\"ltr\">torev</var>.",
- "apihelp-compare-param-tosection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'to'.",
"apihelp-compare-param-topst": "לעשות התמרה לפני שמירה ב־<var>totext</var>.",
+ "apihelp-compare-param-totext": "להשתמש בטקסט הזה במקום התוכן של הגרסה שהוגדר ב־<var dir=\"ltr\">totitle</var>, <var dir=\"ltr\">toid</var> or <var dir=\"ltr\">torev</var>.",
"apihelp-compare-param-tocontentmodel": "מודל התוכן של <var>totext</var>. אם זה לא סופק, ייעשה ניחוש על סמך פרמטרים אחרים.",
"apihelp-compare-param-tocontentformat": "תסדיר הסדרת תוכן של <var>fromtext</var>.",
+ "apihelp-compare-param-tosection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'to'.",
"apihelp-compare-param-prop": "אילו פריטי מידע לקבל.",
"apihelp-compare-paramvalue-prop-diff": "ה־HTML של ההשוואה.",
"apihelp-compare-paramvalue-prop-diffsize": "גודל ה־HTML של ההשוואה, בבתים.",
"Dj"
]
},
- "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Dokumentáció]]\n* [[mw:Special:MyLanguage/API:FAQ|GYIK]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Levelezőlista]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-bejelentések]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Hibabejelentések és kérések]\n</div>\n<strong>Státusz:</strong> Minden ezen a lapon látható funkciónak működnie kell, de az API jelenleg is aktív fejlesztés alatt áll, és bármikor változhat. Iratkozz fel a [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce levelezőlistára] a frissítések követéséhez.\n\n<strong>Hibás kérések:</strong> Ha az API hibás kérést kap, egy HTTP-fejlécet küld vissza „MediaWiki-API-Error” kulccsal, és a fejléc értéke és a visszaküldött hibakód ugyanarra az értékre lesz állítva. További információért lásd: [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Hibák és figyelmeztetések]].\n\n<p class=\"mw-apisandbox-link\"><strong>Tesztelés:</strong> Az API-kérések könnyebb teszteléséhez használható az [[Special:ApiSandbox|API-homokozó]].</p>",
+ "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Dokumentáció]]\n* [[mw:Special:MyLanguage/API:FAQ|GYIK]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Levelezőlista]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-bejelentések]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Hibabejelentések és kérések]\n</div>\n<strong>Állapot:</strong> A MediaWiki API egy érett és stabil interfész, ami aktív támogatásban és fejlesztésben részesül. Bár próbáljuk elkerülni, de néha szükség van visszafelé nem kompatibilis változtatásokra; iratkozz fel a [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce levelezőlistára] a frissítések követéséhez.\n\n<strong>Hibás kérések:</strong> Ha az API hibás kérést kap, egy HTTP-fejlécet küld vissza „MediaWiki-API-Error” kulccsal, és a fejléc értéke és a visszaküldött hibakód ugyanarra az értékre lesz állítva. További információért lásd: [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Hibák és figyelmeztetések]].\n\n<p class=\"mw-apisandbox-link\"><strong>Tesztelés:</strong> Az API-kérések könnyebb teszteléséhez használható az [[Special:ApiSandbox|API-homokozó]].</p>",
"apihelp-main-param-action": "Milyen műveletet hajtson végre.",
"apihelp-main-param-format": "A kimenet formátuma.",
"apihelp-main-param-smaxage": "Az <code>s-maxage</code> gyorsítótár-vezérlő HTTP-fejléc beállítása ennyi másodpercre. A hibák soha nincsenek gyorsítótárazva.",
"Kkairri",
"ネイ",
"Omotecho",
- "Yusuke1109"
+ "Yusuke1109",
+ "Suyama"
]
},
"apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|説明文書]]\n* [[mw:Special:MyLanguage/API:FAQ|よくある質問]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api メーリングリスト]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API 告知]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R バグの報告とリクエスト]\n</div>\n<strong>状態:</strong> MediaWiki APIは、積極的にサポートされ、改善された成熟した安定したインターフェースです。避けようとはしていますが、時には壊れた変更が加えられるかもしれません。アップデートの通知を受け取るには、[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ the mediawiki-api-announce メーリングリスト]に参加してください。\n\n<strong>誤ったリクエスト:</strong> 誤ったリクエストが API に送られた場合、\"MediaWiki-API-Error\" HTTP ヘッダーが送信され、そのヘッダーの値と送り返されるエラーコードは同じ値にセットされます。より詳しい情報は [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Errors and warnings]] を参照してください。\n\n<p class=\"mw-apisandbox-link\"><strong>テスト:</strong> API のリクエストのテストは、[[Special:ApiSandbox]]で簡単に行えます。</p>",
"apihelp-compare-param-fromtitle": "比較する1つ目のページ名。",
"apihelp-compare-param-fromid": "比較する1つ目のページID。",
"apihelp-compare-param-fromrev": "比較する1つ目の版。",
- "apihelp-compare-param-fromtext": "<var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var> で指定された版の内容の代わりに、このテキストを使用します。",
- "apihelp-compare-param-fromsection": "'from' の内容のうち指定された節のみを使用します。",
"apihelp-compare-param-frompst": "<var>fromtext</var>に保存前変換を行います。",
+ "apihelp-compare-param-fromtext": "<var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var> で指定された版の内容の代わりに、このテキストを使用します。",
"apihelp-compare-param-fromcontentmodel": "<var>fromtext</var>のコンテンツモデル。指定されていない場合は、他のパラメータに基づいて推測されます。",
+ "apihelp-compare-param-fromsection": "'from' の内容のうち指定された節のみを使用します。",
"apihelp-compare-param-totitle": "比較する2つ目のページ名。",
"apihelp-compare-param-toid": "比較する2つ目のページID。",
"apihelp-compare-param-torev": "比較する2つ目の版。",
- "apihelp-compare-param-tosection": "'to' の内容のうち指定された節のみを使用します。",
"apihelp-compare-param-topst": "<var>totext</var>に保存前変換を行います。",
"apihelp-compare-param-tocontentmodel": "<var>totext</var> のコンテンツモデル。指定されていない場合は、他のパラメータに基づいて推測されます。",
+ "apihelp-compare-param-tosection": "'to' の内容のうち指定された節のみを使用します。",
"apihelp-compare-param-prop": "どの情報を取得するか:",
"apihelp-compare-paramvalue-prop-diff": "差分HTML。",
"apihelp-compare-paramvalue-prop-diffsize": "差分HTMLのサイズ (バイト数)。",
"api-help-permissions": "{{PLURAL:$1|権限}}:",
"api-help-permissions-granted-to": "{{PLURAL:$1|権限を持つグループ}}: $2",
"api-help-open-in-apisandbox": "<small>[サンドボックスで開く]</small>",
+ "apierror-botsnotsupported": "この API インターフェースはボットをサポートしていません。",
"apierror-filedoesnotexist": "ファイルが存在しません。",
"apierror-invaliduser": "無効なユーザー名「$1」。",
"apierror-missingparam": "パラメーター <var>$1</var> を設定してください。",
"apihelp-compare-param-fromtitle": "비교할 첫 이름.",
"apihelp-compare-param-fromid": "비교할 첫 문서 ID.",
"apihelp-compare-param-fromrev": "비교할 첫 판.",
- "apihelp-compare-param-fromtext": "<var>fromtitle</var>, <var>fromid</var> 또는 <var>fromrev</var>로 지정된 판의 내용 대신 이 텍스트를 사용합니다.",
+ "apihelp-compare-param-frompst": "<var>fromtext-{slot}</var>에 사전 저장 변환을 수행합니다.",
+ "apihelp-compare-param-fromtext-{slot}": "지정된 슬롯의 텍스트입니다. 생략할 경우 판에서 슬롯이 제거됩니다.",
+ "apihelp-compare-param-fromcontentmodel-{slot}": "<var>fromtext-{slot}</var>의 콘텐츠 모델입니다. 지정하지 않으면 다른 변수를 참고하여 추정합니다.",
+ "apihelp-compare-param-fromcontentformat-{slot}": "<var>fromtext-{slot}</var>의 콘텐츠 직렬화 포맷입니다.",
+ "apihelp-compare-param-fromtext": "<kbd>fromslots=main</kbd>을 지정하고 <var>fromtext-main</var>을 대신 사용합니다.",
+ "apihelp-compare-param-fromcontentmodel": "<kbd>fromslots=main</kbd>을 지정하고 <var>fromcontentmodel-main</var>을 대신 사용합니다.",
+ "apihelp-compare-param-fromcontentformat": "<kbd>fromslots=main</kbd>을 지정하고 <var>fromcontentformat-main</var>을 대신 사용합니다.",
"apihelp-compare-param-fromsection": "지정된 'from' 내용의 지정된 문단만 사용합니다.",
- "apihelp-compare-param-frompst": "<var>fromtext</var>에 사전 저장 변환을 수행합니다.",
- "apihelp-compare-param-fromcontentmodel": "<var>fromtext</var>의 콘텐츠 모델입니다. 지정하지 않으면 다른 변수를 참고하여 추정합니다.",
- "apihelp-compare-param-fromcontentformat": "<var>fromtext</var>의 콘텐츠 직렬화 포맷입니다.",
"apihelp-compare-param-totitle": "비교할 두 번째 제목.",
"apihelp-compare-param-toid": "비교할 두 번째 문서 ID.",
"apihelp-compare-param-torev": "비교할 두 번째 판.",
"apihelp-compare-param-torelative": "<var>fromtitle</var>, <var>fromid</var> 또는 <var>fromrev</var>에서 결정된 판과 상대적인 판을 사용합니다. 다른 'to' 옵션들은 모두 무시됩니다.",
- "apihelp-compare-param-totext": "<var>totitle</var>, <var>toid</var> 또는 <var>torev</var>로 지정된 판의 내용 대신 이 텍스트를 사용합니다.",
- "apihelp-compare-param-tosection": "지정된 'to' 내용의 지정된 문단만 사용합니다.",
"apihelp-compare-param-topst": "<var>totext</var>에 사전 저장 변환을 수행합니다.",
- "apihelp-compare-param-tocontentmodel": "<var>totext</var>의 콘텐츠 모델입니다. 지정하지 않으면 다른 변수를 참고하여 추정합니다.",
- "apihelp-compare-param-tocontentformat": "<var>totext</var>의 콘텐츠 직렬화 포맷입니다.",
+ "apihelp-compare-param-totext-{slot}": "지정된 슬롯의 텍스트입니다. 생략하면 이 슬롯은 판에서 제거됩니다.",
+ "apihelp-compare-param-tocontentmodel-{slot}": "<var>totext-{slot}</var>의 콘텐츠 모델입니다. 지정하지 않으면 다른 변수를 참고하여 추정합니다.",
+ "apihelp-compare-param-tocontentformat-{slot}": "<var>totext-{slot}</var>의 콘텐츠 직렬화 포맷입니다.",
+ "apihelp-compare-param-totext": "<kbd>toslots=main</kbd>을 지정하고 <var>totext-main</var>을 대신 사용합니다.",
+ "apihelp-compare-param-tocontentmodel": "<kbd>toslots=main</kbd>을 지정하고 <var>tocontentmodel-main</var>을 대신 사용합니다.",
+ "apihelp-compare-param-tocontentformat": "<kbd>toslots=main</kbd>을 지정하고 <var>tocontentformat-main</var>을 대신 사용합니다.",
+ "apihelp-compare-param-tosection": "지정된 'to' 내용의 지정된 문단만 사용합니다.",
"apihelp-compare-param-prop": "가져올 정보입니다.",
"apihelp-compare-paramvalue-prop-diff": "HTML의 차이입니다.",
"apihelp-compare-paramvalue-prop-diffsize": "HTML 차이의 크기(바이트 단위)입니다.",
"apierror-maxlag-generic": "데이터베이스 서버 대기 중: $1 {{PLURAL:$1|초}} 지연되었습니다.",
"apierror-maxlag": "$2 대기 중: $1 {{PLURAL:$1|초}} 지연되었습니다.",
"apierror-missingcontent-revid": "ID $1 판에 해당하는 내용이 없습니다.",
+ "apierror-missingcontent-revid-role": "역할 $2에 대해 판 ID $1의 내용이 없습니다.",
"apierror-missingparam": "<var>$1</var> 변수는 설정해야 합니다.",
"apierror-missingtitle": "지정한 페이지가 존재하지 않습니다.",
"apierror-missingtitle-byname": "$1 문서가 존재하지 않습니다.",
"apihelp-compare-param-fromtitle": "Første tittel å sammenligne.",
"apihelp-compare-param-fromid": "Første side-ID å sammenligne.",
"apihelp-compare-param-fromrev": "Første revisjon å sammenligne.",
- "apihelp-compare-param-fromtext": "Bruk denne teksten i stedet for innholdet i revisjonen som angis med <var>fromtitle</var>, <var>fromid</var> eller <var>fromrev</var>.",
"apihelp-compare-param-frompst": "Gjør en transformering av <var>fromtext</var> før lagring.",
+ "apihelp-compare-param-fromtext": "Bruk denne teksten i stedet for innholdet i revisjonen som angis med <var>fromtitle</var>, <var>fromid</var> eller <var>fromrev</var>.",
"apihelp-compare-param-fromcontentmodel": "Innholdsmodell for <var>fromtext</var>. Om den ikke angis vil den gjettes basert på de andre parameterne.",
"apihelp-compare-param-fromcontentformat": "Innholdsserialiseringsformat for <var>fromtext</var>.",
"apihelp-compare-param-totitle": "Andre tittel å sammenligne.",
"apihelp-compare-param-toid": "Andre side-ID å sammenligne.",
"apihelp-compare-param-torev": "Andre revisjon å sammenligne.",
"apihelp-compare-param-torelative": "Bruk en revisjon som er relativ til revisjonen som hentes fra <var>fromtitle</var>, <var>fromid</var> eller <var>fromrev</var>. Alle de andre «to»-alternativene vil ignoreres.",
- "apihelp-compare-param-totext": "Bruk denne teksten i stedet for innholdet i revisjonen spesifisert av <var>totitle</var>, <var>toid</var> eller <var>torev</var>.",
"apihelp-compare-param-topst": "Gjør en transformering av <var>totext</var> før lagring.",
+ "apihelp-compare-param-totext": "Bruk denne teksten i stedet for innholdet i revisjonen spesifisert av <var>totitle</var>, <var>toid</var> eller <var>torev</var>.",
"apihelp-compare-param-tocontentmodel": "Innholdsmodellen til <var>totext</var>. Om denne ikke angis vil den bli gjettet ut fra andre parametere.",
"apihelp-compare-param-tocontentformat": "Innholdsserialiseringsformat for <var>totext</var>.",
"apihelp-compare-param-prop": "Hvilke informasjonsdeler som skal hentes.",
"apihelp-checktoken-example-simple": "Sprawdź poprawność tokenu <kbd>csrf</kbd>.",
"apihelp-clearhasmsg-summary": "Czyści flagę <code>hasmsg</code> dla bieżącego użytkownika.",
"apihelp-clearhasmsg-example-1": "Wyczyść flagę <code>hasmsg</code> dla bieżącego użytkownika.",
- "apihelp-compare-summary": "Zauważ różnicę między dwoma stronami",
+ "apihelp-compare-summary": "Pokaż porównanie dwóch stron.",
"apihelp-compare-param-fromtitle": "Pierwszy tytuł do porównania.",
"apihelp-compare-param-fromid": "ID pierwszej strony do porównania.",
"apihelp-compare-param-fromrev": "Pierwsza wersja do porównania.",
"apihelp-compare-param-fromtitle": "Primeiro título para comparar.",
"apihelp-compare-param-fromid": "Primeiro ID de página para comparar.",
"apihelp-compare-param-fromrev": "Primeira revisão para comparar.",
- "apihelp-compare-param-fromtext": "Use este texto em vez do conteúdo da revisão especificada por <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>.",
+ "apihelp-compare-param-frompst": "Fazer uma transformação anterior à gravação, de <var>fromtext-{slot}</var>.",
+ "apihelp-compare-param-fromslots": "Substituir o conteúdo da revisão especificada por <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>.\n\nEste parâmetro especifica os segmentos que deverão ser modificados. Use <var>fromtext-{slot}</var>, <var>fromcontentmodel-{slot}</var> e <var>fromcontentformat-{slot}</var> para especificar conteúdo para cada segmento.",
+ "apihelp-compare-param-fromtext-{slot}": "Texto do slot especificado. Se omitido, o slot é removido da revisão.",
+ "apihelp-compare-param-fromsection-{slot}": "Quando <var>fromtext-{slot}</var> é o conteúdo de uma única secção, este é o número da seção. Será fundido na revisão especificada por <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var> tal como acontece na edição de uma secção.",
+ "apihelp-compare-param-fromcontentmodel-{slot}": "Modelo de conteúdo de <var>fromtext-{slot}</var>. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.",
+ "apihelp-compare-param-fromcontentformat-{slot}": "Formato de serialização de conteúdo de <var>fromtext-{slot}</var>.",
+ "apihelp-compare-param-fromtext": "Especificar <kbd>fromslots=main</kbd> e usar <var>fromtext-main</var>.",
+ "apihelp-compare-param-fromcontentmodel": "Especificar <kbd>fromslots=main</kbd> e usar <var>fromcontentmodel-main</var>.",
+ "apihelp-compare-param-fromcontentformat": "Especificar <kbd>fromslots=main</kbd> e usar <var>fromcontentformat-main</var>.",
"apihelp-compare-param-fromsection": "Utilizar apenas a secção especificada do conteúdo 'from' especificado.",
- "apihelp-compare-param-frompst": "Faz uma transformação pré-salvar em <var>fromtext</var>.",
- "apihelp-compare-param-fromcontentmodel": "Modelo de conteúdo de <var>fromtext</var>. Se não for fornecido, será adivinhado com base nos outros parâmetros.",
- "apihelp-compare-param-fromcontentformat": "Formato de serialização de conteúdo de <var>fromtext</var>.",
"apihelp-compare-param-totitle": "Segundo título para comparar.",
"apihelp-compare-param-toid": "Segundo ID de página para comparar.",
"apihelp-compare-param-torev": "Segunda revisão para comparar.",
"apihelp-compare-param-torelative": "Use uma revisão relativa à revisão determinada de <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>. Todas as outras opções 'to' serão ignoradas.",
- "apihelp-compare-param-totext": "Use este texto em vez do conteúdo da revisão especificada por <var>totitle</var>, <var>toid</var> ou <var>torev</var>.",
- "apihelp-compare-param-tosection": "Utilizar apenas a secção especificada do conteúdo 'to' especificado.",
"apihelp-compare-param-topst": "Faz uma transformação pré-salvar em <var>totext</var>.",
- "apihelp-compare-param-tocontentmodel": "Modelo de conteúdo de <var>totext</var>. Se não for fornecido, será adivinhado com base nos outros parâmetros.",
- "apihelp-compare-param-tocontentformat": "Formato de serialização de conteúdo de <var>totext</var>.",
+ "apihelp-compare-param-totext-{slot}": "Texto do slot especificado. Se omitido, o slot é removido da revisão.",
+ "apihelp-compare-param-tosection-{slot}": "Quando <var>totext-{slot}</var> é o conteúdo de uma única secção, este é o número da secção. Será fundido na revisão especificada por <var>totitle</var>, <var>toid</var> ou <var>torev</var> tal como acontece na edição de uma secção.",
+ "apihelp-compare-param-tocontentmodel-{slot}": "Modelo de conteúdo de <var>totext-{slot}</var>. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.",
+ "apihelp-compare-param-tocontentformat-{slot}": "Formato de seriação do conteúdo de <var>totext-{slot}</var>.",
+ "apihelp-compare-param-totext": "Especificar <kbd>toslots=main</kbd> e usar <var>totext-main</var>.",
+ "apihelp-compare-param-tocontentmodel": "Especificar <kbd>toslots=main</kbd> e usar <var>tocontentmodel-main</var>.",
+ "apihelp-compare-param-tocontentformat": "Especificar <kbd>toslots=main</kbd> e usar <var>tocontentformat-main</var>.",
+ "apihelp-compare-param-tosection": "Utilizar apenas a secção especificada do conteúdo 'to' especificado.",
"apihelp-compare-param-prop": "Quais peças de informação incluir.",
"apihelp-compare-paramvalue-prop-diff": "O dif do HTML.",
"apihelp-compare-paramvalue-prop-diffsize": "O tamanho do diff HTML, em bytes.",
"apihelp-compare-paramvalue-prop-comment": "O comentário das revisões 'from' e 'to'.",
"apihelp-compare-paramvalue-prop-parsedcomment": "O comentário analisado sobre as revisões 'from' e 'to'.",
"apihelp-compare-paramvalue-prop-size": "O tamanho das revisões 'from' e 'to'.",
+ "apihelp-compare-param-slots": "Devolve os diffs individuais para estes slots, em vez de um diff combinado para todos os slots.",
"apihelp-compare-example-1": "Criar um diff entre a revisão 1 e 2.",
"apihelp-createaccount-summary": "Criar uma nova conta de usuário.",
"apihelp-createaccount-param-preservestate": "Se <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> retornar true para <samp>hasprimarypreservedstate</samp>, pedidos marcados como <samp>hasprimarypreservedstate</samp> devem ser omitidos. Se retornou um valor não vazio para <samp>preservedusername</samp>, esse nome de usuário deve ser usado pelo parâmetro <var>username</var>.",
"apierror-compare-no-title": "Não é possível pré-salvar a transformação sem um título. Tente especificar <var>fromtitle</var> ou <var>totitle</var>.",
"apierror-compare-nosuchfromsection": "Não há nenhuma secção $1 no conteúdo 'from'.",
"apierror-compare-nosuchtosection": "Não há nenhuma seção $1 no conteúdo 'to'.",
+ "apierror-compare-nofromrevision": "Não foi especificada uma revisão 'from'. Especificar <var>fromrev</var>, <var>fromtitle</var> ou <var>fromid</var>.",
+ "apierror-compare-notorevision": "Não foi especificada uma revisão 'to'. Especificar <var>torev</var>, <var>totitle</var> ou <var>toid</var>.",
"apierror-compare-relative-to-nothing": "Nenhuma revisão 'from' para <var>torelative</var> para ser relativa à.",
"apierror-contentserializationexception": "Falha na serialização de conteúdo: $1",
"apierror-contenttoobig": "O conteúdo fornecido excede o limite de tamanho do artigo de $1 {{PLURAL: $1|kilobyte|kilobytes}}.",
"apierror-mimesearchdisabled": "A pesquisa MIME está desativada no Miser Mode.",
"apierror-missingcontent-pageid": "Falta conteúdo para a ID da página $1.",
"apierror-missingcontent-revid": "Falta conteúdo para a ID de revisão $1.",
+ "apierror-missingcontent-revid-role": "Conteúdo ausente para o ID de revisão $1 para a função $2.",
"apierror-missingparam-at-least-one-of": "{{PLURAL:$2|O parâmetro|Ao menos um dos parâmetros}} $1 é necessário.",
"apierror-missingparam-one-of": "{{PLURAL:$2|O parâmetro|Um dos parâmetros}} $1 é necessário.",
"apierror-missingparam": "O parâmetro <var>$1</var> precisa ser definido.",
"apihelp-compare-param-fromtitle": "Primeiro título a comparar.",
"apihelp-compare-param-fromid": "Primeiro identificador de página a comparar.",
"apihelp-compare-param-fromrev": "Primeira revisão a comparar.",
- "apihelp-compare-param-fromtext": "Usar este texto em vez do conteúdo da revisão especificada por <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>.",
+ "apihelp-compare-param-frompst": "Fazer uma transformação anterior à gravação, de <var>fromtext-{slot}</var>.",
+ "apihelp-compare-param-fromslots": "Substituir o conteúdo da revisão especificada por <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>.\n\nEste parâmetro especifica os segmentos que deverão ser modificados. Use <var>fromtext-{slot}</var>, <var>fromcontentmodel-{slot}</var> e <var>fromcontentformat-{slot}</var> para especificar conteúdo para cada segmento.",
+ "apihelp-compare-param-fromtext-{slot}": "Texto do segmento especificado. Se for omitido, o segmento é removido da revisão.",
+ "apihelp-compare-param-fromsection-{slot}": "Quando <var>fromtext-{slot}</var> é o conteúdo de uma única secção, este é o número da secção. Será fundido na revisão especificada por <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var> tal como acontece na edição de uma secção.",
+ "apihelp-compare-param-fromcontentmodel-{slot}": "Modelo de conteúdo de <var>fromtext-{slot}</var>. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.",
+ "apihelp-compare-param-fromcontentformat-{slot}": "Formato de seriação do conteúdo de <var>fromtext-{slot}</var>.",
+ "apihelp-compare-param-fromtext": "Especificar <kbd>fromslots=main</kbd> e usar <var>fromtext-main</var>.",
+ "apihelp-compare-param-fromcontentmodel": "Especificar <kbd>fromslots=main</kbd> e usar <var>fromcontentmodel-main</var>.",
+ "apihelp-compare-param-fromcontentformat": "Especificar <kbd>fromslots=main</kbd> e usar <var>fromcontentformat-main</var>.",
"apihelp-compare-param-fromsection": "Utilizar apenas a secção especificada do conteúdo 'from' especificado.",
- "apihelp-compare-param-frompst": "Fazer uma transformação anterior à gravação, de <var>fromtext</var>.",
- "apihelp-compare-param-fromcontentmodel": "Modelo de conteúdo de <var>fromtext</var>. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.",
- "apihelp-compare-param-fromcontentformat": "Formato de seriação do conteúdo de <var>fromtext</var>.",
"apihelp-compare-param-totitle": "Segundo título a comparar.",
"apihelp-compare-param-toid": "Segundo identificador de página a comparar.",
"apihelp-compare-param-torev": "Segunda revisão a comparar.",
"apihelp-compare-param-torelative": "Usar uma revisão relativa à revisão determinada a partir de <var>fromtitle</var>, <var>fromid</var> ou <var>fromrev</var>. Todas as outras opções 'to' serão ignoradas.",
- "apihelp-compare-param-totext": "Usar este texto em vez do conteúdo da revisão especificada por <var>totitle</var>, <var>toid</var> ou <var>torev</var>.",
- "apihelp-compare-param-tosection": "Utilizar apenas a secção especificada do conteúdo 'to' especificado.",
"apihelp-compare-param-topst": "Fazer uma transformação anterior à gravação, de <var>totext</var>.",
- "apihelp-compare-param-tocontentmodel": "Modelo de conteúdo de <var>totext</var>. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.",
- "apihelp-compare-param-tocontentformat": "Formato de seriação do conteúdo de <var>totext</var>.",
+ "apihelp-compare-param-toslots": "Especificar o conteúdo para ser usado em vez do conteúdo da revisão especificada em <var>totitle</var>, <var>toid</var> ou <var>torev</var>.\n\nEste parâmetro especifica os segmentos que têm conteúdo. Use <var>totext-{slot}</var>, <var>tocontentmodel-{slot}</var> e <var>tocontentformat-{slot}</var> para especificar conteúdo para cada segmento.",
+ "apihelp-compare-param-totext-{slot}": "Texto do segmento especificado.",
+ "apihelp-compare-param-tosection-{slot}": "Quando <var>totext-{slot}</var> é o conteúdo de uma única secção, este é o número da secção. Será fundido na revisão especificada por <var>totitle</var>, <var>toid</var> ou <var>torev</var> tal como acontece na edição de uma secção.",
+ "apihelp-compare-param-tocontentmodel-{slot}": "Modelo de conteúdo de <var>totext-{slot}</var>. Se não for fornecido, ele será deduzido a partir dos outros parâmetros.",
+ "apihelp-compare-param-tocontentformat-{slot}": "Formato de seriação do conteúdo de <var>totext-{slot}</var>.",
+ "apihelp-compare-param-totext": "Especificar <kbd>toslots=main</kbd> e usar <var>totext-main</var>.",
+ "apihelp-compare-param-tocontentmodel": "Especificar <kbd>toslots=main</kbd> e usar <var>tocontentmodel-main</var>.",
+ "apihelp-compare-param-tocontentformat": "Especificar <kbd>toslots=main</kbd> e usar <var>tocontentformat-main</var>.",
+ "apihelp-compare-param-tosection": "Utilizar apenas a secção especificada do conteúdo 'to' especificado.",
"apihelp-compare-param-prop": "As informações que devem ser obtidas.",
"apihelp-compare-paramvalue-prop-diff": "O HTML da lista de diferenças.",
"apihelp-compare-paramvalue-prop-diffsize": "O tamanho do HTML da lista de diferenças, em bytes.",
"apihelp-compare-paramvalue-prop-comment": "O comentário das revisões 'from' e 'to'.",
"apihelp-compare-paramvalue-prop-parsedcomment": "O comentário após análise sintática, das revisões 'from' e 'to'.",
"apihelp-compare-paramvalue-prop-size": "O tamanho das revisões 'from' e 'to'.",
+ "apihelp-compare-param-slots": "Devolver as diferenças individuais destes segmentos, em vez de uma lista combinada para todos os segmentos.",
"apihelp-compare-example-1": "Criar uma lista de diferenças entre as revisões 1 e 2.",
"apihelp-createaccount-summary": "Criar uma conta de utilizador nova.",
"apihelp-createaccount-param-preservestate": "Se <kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd> devolveu o valor verdadeiro para <samp>hasprimarypreservedstate</samp>, pedidos marcados como <samp>primary-required</samp> devem ser omitidos. Se devolveu um valor não vazio em <samp>preservedusername</samp>, esse nome de utilizador tem de ser usado no parâmetro <var>username</var>.",
"apierror-compare-no-title": "Não é possível transformar antes da gravação, sem um título. Tente especificar <var>fromtitle</var> ou <var>totitle</var>.",
"apierror-compare-nosuchfromsection": "Não há nenhuma secção $1 no conteúdo 'from'.",
"apierror-compare-nosuchtosection": "Não há nenhuma secção $1 no conteúdo 'to'.",
+ "apierror-compare-nofromrevision": "Não foi especificada uma revisão 'from'. Especificar <var>fromrev</var>, <var>fromtitle</var> ou <var>fromid</var>.",
+ "apierror-compare-notorevision": "Não foi especificada uma revisão 'to'. Especificar <var>torev</var>, <var>totitle</var> ou <var>toid</var>.",
"apierror-compare-relative-to-nothing": "Não existe uma revisão 'from' em relação à qual <var>torelative</var> possa ser relativo.",
"apierror-contentserializationexception": "A seriação do conteúdo falhou: $1",
"apierror-contenttoobig": "O conteúdo que forneceu excede o tamanho máximo dos artigos que é $1 {{PLURAL:$1|kilobyte|kilobytes}}.",
"apierror-mimesearchdisabled": "A pesquisa MIME é desativada no modo avarento.",
"apierror-missingcontent-pageid": "Conteúdo em falta para a página com o identificador $1.",
"apierror-missingcontent-revid": "Conteúdo em falta para a revisão com o identificador $1.",
+ "apierror-missingcontent-revid-role": "O identificador de revisão $1 para a função $2 não tem conteúdo.",
"apierror-missingparam-at-least-one-of": "{{PLURAL:$2|O parâmetro|Pelo menos um dos parâmetros}} $1 é obrigatório.",
"apierror-missingparam-one-of": "{{PLURAL:$2|O parâmetro|Um dos parâmetros}} $1 é obrigatório.",
"apierror-missingparam": "O parâmetro <var>$1</var> tem de ser definido.",
"apihelp-compare-param-fromtitle": "{{doc-apihelp-param|compare|fromtitle}}",
"apihelp-compare-param-fromid": "{{doc-apihelp-param|compare|fromid}}",
"apihelp-compare-param-fromrev": "{{doc-apihelp-param|compare|fromrev}}",
- "apihelp-compare-param-fromtext": "{{doc-apihelp-param|compare|fromtext}}",
- "apihelp-compare-param-fromsection": "{{doc-apihelp-param|compare|fromsection}}",
"apihelp-compare-param-frompst": "{{doc-apihelp-param|compare|frompst}}",
+ "apihelp-compare-param-fromslots": "{{doc-apihelp-param|compare|fromslots}}",
+ "apihelp-compare-param-fromtext-{slot}": "{{doc-apihelp-param|compare|fromtext-{slot} }}",
+ "apihelp-compare-param-fromsection-{slot}": "{{doc-apihelp-param|compare|fromsection-{slot} }}",
+ "apihelp-compare-param-fromcontentmodel-{slot}": "{{doc-apihelp-param|compare|fromcontentmodel-{slot} }}",
+ "apihelp-compare-param-fromcontentformat-{slot}": "{{doc-apihelp-param|compare|fromcontentformat-{slot} }}",
+ "apihelp-compare-param-fromtext": "{{doc-apihelp-param|compare|fromtext}}",
"apihelp-compare-param-fromcontentmodel": "{{doc-apihelp-param|compare|fromcontentmodel}}",
"apihelp-compare-param-fromcontentformat": "{{doc-apihelp-param|compare|fromcontentformat}}",
+ "apihelp-compare-param-fromsection": "{{doc-apihelp-param|compare|fromsection}}",
"apihelp-compare-param-totitle": "{{doc-apihelp-param|compare|totitle}}",
"apihelp-compare-param-toid": "{{doc-apihelp-param|compare|toid}}",
"apihelp-compare-param-torev": "{{doc-apihelp-param|compare|torev}}",
"apihelp-compare-param-torelative": "{{doc-apihelp-param|compare|torelative}}",
- "apihelp-compare-param-totext": "{{doc-apihelp-param|compare|totext}}",
- "apihelp-compare-param-tosection": "{{doc-apihelp-param|compare|tosection}}",
"apihelp-compare-param-topst": "{{doc-apihelp-param|compare|topst}}",
+ "apihelp-compare-param-toslots": "{{doc-apihelp-param|compare|toslots}}",
+ "apihelp-compare-param-totext-{slot}": "{{doc-apihelp-param|compare|totext-{slot} }}",
+ "apihelp-compare-param-tosection-{slot}": "{{doc-apihelp-param|compare|tosection-{slot} }}",
+ "apihelp-compare-param-tocontentmodel-{slot}": "{{doc-apihelp-param|compare|tocontentmodel-{slot} }}",
+ "apihelp-compare-param-tocontentformat-{slot}": "{{doc-apihelp-param|compare|tocontentformat-{slot} }}",
+ "apihelp-compare-param-totext": "{{doc-apihelp-param|compare|totext}}",
"apihelp-compare-param-tocontentmodel": "{{doc-apihelp-param|compare|tocontentmodel}}",
"apihelp-compare-param-tocontentformat": "{{doc-apihelp-param|compare|tocontentformat}}",
+ "apihelp-compare-param-tosection": "{{doc-apihelp-param|compare|tosection}}",
"apihelp-compare-param-prop": "{{doc-apihelp-param|compare|prop}}",
"apihelp-compare-paramvalue-prop-diff": "{{doc-apihelp-paramvalue|compare|prop|diff}}",
"apihelp-compare-paramvalue-prop-diffsize": "{{doc-apihelp-paramvalue|compare|prop|diffsize}}",
"apihelp-compare-paramvalue-prop-comment": "{{doc-apihelp-paramvalue|compare|prop|comment}}",
"apihelp-compare-paramvalue-prop-parsedcomment": "{{doc-apihelp-paramvalue|compare|prop|parsedcomment}}",
"apihelp-compare-paramvalue-prop-size": "{{doc-apihelp-paramvalue|compare|prop|size}}",
+ "apihelp-compare-param-slots": "{{doc-apihelp-param|compare|slots}}",
"apihelp-compare-example-1": "{{doc-apihelp-example|compare}}",
"apihelp-createaccount-summary": "{{doc-apihelp-summary|createaccount}}",
"apihelp-createaccount-param-preservestate": "{{doc-apihelp-param|createaccount|preservestate|info=This message is displayed in addition to {{msg-mw|api-help-authmanagerhelper-preservestate}}.}}",
"apierror-compare-no-title": "{{doc-apierror}}",
"apierror-compare-nosuchfromsection": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.",
"apierror-compare-nosuchtosection": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.",
+ "apierror-compare-nofromrevision": "{{doc-apierror}}",
+ "apierror-compare-notorevision": "{{doc-apierror}}",
"apierror-compare-relative-to-nothing": "{{doc-apierror}}",
"apierror-contentserializationexception": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception text, may end with punctuation. Currently this is probably English, hopefully we'll fix that in the future.",
"apierror-contenttoobig": "{{doc-apierror}}\n\nParameters:\n* $1 - Maximum article size in kilobytes.",
"apierror-mimesearchdisabled": "{{doc-apierror}}",
"apierror-missingcontent-pageid": "{{doc-apierror}}\n\nParameters:\n* $1 - Page ID number.",
"apierror-missingcontent-revid": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number",
+ "apierror-missingcontent-revid-role": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number\n* $2 - Role name",
"apierror-missingparam-at-least-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - List of parameter names.\n* $2 - Number of parameters.",
"apierror-missingparam-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - List of parameter names.\n* $2 - Number of parameters.",
"apierror-missingparam": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
"apihelp-compare-param-fromtitle": "Заголовок первой сравниваемой страницы.",
"apihelp-compare-param-fromid": "Идентификатор первой сравниваемой страницы.",
"apihelp-compare-param-fromrev": "Первая сравниваемая версия.",
- "apihelp-compare-param-fromtext": "Используйте этот текст вместо содержимого версии, заданной <var>fromtitle</var>, <var>fromid</var> или <var>fromrev</var>.",
- "apihelp-compare-param-fromsection": "Использовать только указанную секцию из содержимого «from».",
"apihelp-compare-param-frompst": "Выполнить преобразование перед записью правки (PST) над <var>fromtext</var>.",
+ "apihelp-compare-param-fromtext": "Используйте этот текст вместо содержимого версии, заданной <var>fromtitle</var>, <var>fromid</var> или <var>fromrev</var>.",
"apihelp-compare-param-fromcontentmodel": "Модель содержимого <var>fromtext</var>. Если не задана, будет угадана по другим параметрам.",
"apihelp-compare-param-fromcontentformat": "Формат сериализации содержимого <var>fromtext</var>.",
+ "apihelp-compare-param-fromsection": "Использовать только указанную секцию из содержимого «from».",
"apihelp-compare-param-totitle": "Заголовок второй сравниваемой страницы.",
"apihelp-compare-param-toid": "Идентификатор второй сравниваемой страницы.",
"apihelp-compare-param-torev": "Вторая сравниваемая версия.",
"apihelp-compare-param-torelative": "Использовать версию, относящуюся к определённой <var>fromtitle</var>, <var>fromid</var> или <var>fromrev</var>. Все другие опции 'to' будут проигнорированы.",
- "apihelp-compare-param-totext": "Используйте этот текст вместо содержимого версии, заданной <var>totitle</var>, <var>toid</var> или <var>torev</var>.",
- "apihelp-compare-param-tosection": "Использовать только указанную секцию из содержимого «to».",
"apihelp-compare-param-topst": "Выполнить преобразование перед записью правки (PST) над <var>totext</var>.",
+ "apihelp-compare-param-tocontentmodel-{slot}": "Модель содержимого <var>totext-{slot}</var>. Если не задана, будет угадана по другим параметрам.",
+ "apihelp-compare-param-totext": "Используйте этот текст вместо содержимого версии, заданной <var>totitle</var>, <var>toid</var> или <var>torev</var>.",
"apihelp-compare-param-tocontentmodel": "Модель содержимого <var>totext</var>. Если не задана, будет угадана по другим параметрам.",
"apihelp-compare-param-tocontentformat": "Формат сериализации содержимого <var>totext</var>.",
+ "apihelp-compare-param-tosection": "Использовать только указанную секцию из содержимого «to».",
"apihelp-compare-param-prop": "Какую информацию получить.",
"apihelp-compare-paramvalue-prop-diff": "HTML-код разницы.",
"apihelp-compare-paramvalue-prop-diffsize": "Размер HTML-кода разницы в байтах.",
"Rockyfelle",
"Macofe",
"Magol",
- "Bengtsson96"
+ "Bengtsson96",
+ "Larske"
]
},
"apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Dokumentation]]\n* [[mw:Special:MyLanguage/API:FAQ|Vanliga frågor]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Sändlista]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-nyheter]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Buggar och begäran]\n</div>\n<strong>Status:</strong> Alla funktioner som visas på denna sida bör fungera, men API:et är fortfarande under utveckling och kan ändras när som helst. Prenumerera på [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ sändlistan mediawiki-api-announce] för uppdateringsaviseringar.\n\n<strong>Felaktiga begäran:</strong> När felaktiga begäran skickas till API:et kommer en HTTP-header skickas med nyckeln \"MediaWiki-API-Error\" och sedan kommer både värdet i headern och felkoden som skickades tillbaka anges som samma värde. För mer information se [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Fel och varningar]].\n\n<p class=\"mw-apisandbox-link\"><strong>Testning:</strong> För enkelt testning av API-begäran, se [[Special:ApiSandbox]].</p>",
"apihelp-query+images-param-limit": "Hur många filer att returnera.",
"apihelp-query+images-param-dir": "Riktningen att lista mot.",
"apihelp-query+images-example-simple": "Hämta en lista över filer som används på [[Main Page]].",
- "apihelp-query+imageusage-summary": "Hitta alla sidor som användare angiven bildtitel.",
+ "apihelp-query+imageusage-summary": "Hitta alla sidor som använder angiven bildtitel.",
"apihelp-query+imageusage-param-dir": "Riktningen att lista mot.",
"apihelp-query+imageusage-example-simple": "Visa sidor med hjälp av [[:File:Albert Einstein Head.jpg]].",
"apihelp-query+imageusage-example-generator": "Hämta information om sidor med hjälp av [[:File:Albert Einstein Head.jpg]].",
"apihelp-compare-param-fromtitle": "Перший заголовок для порівняння.",
"apihelp-compare-param-fromid": "Перший ID сторінки для порівняння.",
"apihelp-compare-param-fromrev": "Перша версія для порівняння.",
- "apihelp-compare-param-fromtext": "Використати цей текст замість контенту версії, вказаної через <var>fromtitle</var>, <var>fromid</var> або <var>fromrev</var>.",
- "apihelp-compare-param-fromsection": "Використовувати лише вказану секцію із заданого вмісту «from».",
"apihelp-compare-param-frompst": "Зробити трансформацію перед збереженням на <var>fromtext</var>.",
+ "apihelp-compare-param-fromtext": "Використати цей текст замість контенту версії, вказаної через <var>fromtitle</var>, <var>fromid</var> або <var>fromrev</var>.",
"apihelp-compare-param-fromcontentmodel": "Контентна модель <var>fromtext</var>. Якщо не вказано, буде використано припущення на основі інших параметрів.",
"apihelp-compare-param-fromcontentformat": "Формат серіалізації контенту <var>fromtext</var>.",
+ "apihelp-compare-param-fromsection": "Використовувати лише вказану секцію із заданого вмісту «from».",
"apihelp-compare-param-totitle": "Другий заголовок для порівняння.",
"apihelp-compare-param-toid": "Другий ID сторінки для порівняння.",
"apihelp-compare-param-torev": "Друга версія для порівняння.",
"apihelp-compare-param-torelative": "Використати версію, яка стосується версії, визначеної через <var>fromtitle</var>, <var>fromid</var> або <var>fromrev</var>. Усі інші опції 'to' буде проігноровано.",
- "apihelp-compare-param-totext": "Використати цей текст замість контенту версії, вказаної через <var>totitle</var>, <var>toid</var> або <var>torev</var>.",
- "apihelp-compare-param-tosection": "Використовувати лише вказану секцію із заданого вмісту «to».",
"apihelp-compare-param-topst": "Виконати трансформацію перед збереженням на <var>totext</var>.",
+ "apihelp-compare-param-totext": "Використати цей текст замість контенту версії, вказаної через <var>totitle</var>, <var>toid</var> або <var>torev</var>.",
"apihelp-compare-param-tocontentmodel": "Контентна модель <var>totext</var>. Якщо не вказано, буде використано припущення на основі інших параметрів.",
"apihelp-compare-param-tocontentformat": "Формат серіалізації контенту <var>totext</var>.",
+ "apihelp-compare-param-tosection": "Використовувати лише вказану секцію із заданого вмісту «to».",
"apihelp-compare-param-prop": "Які уривки інформації отримати.",
"apihelp-compare-paramvalue-prop-diff": "HTML різниці версій.",
"apihelp-compare-paramvalue-prop-diffsize": "Розмір HTML різниці версій, у байтах.",
"apihelp-compare-param-fromtitle": "要比较的第一个标题。",
"apihelp-compare-param-fromid": "要比较的第一个页面 ID。",
"apihelp-compare-param-fromrev": "要比较的第一个修订版本。",
- "apihelp-compare-param-fromtext": "使用该文本而不是由<var>fromtitle</var>、<var>fromid</var>或<var>fromrev</var>指定的修订版本内容。",
- "apihelp-compare-param-fromsection": "只使用指定“from”内容的指定章节。",
"apihelp-compare-param-frompst": "在<var>fromtext</var>执行预保存转变。",
+ "apihelp-compare-param-fromtext": "使用该文本而不是由<var>fromtitle</var>、<var>fromid</var>或<var>fromrev</var>指定的修订版本内容。",
"apihelp-compare-param-fromcontentmodel": "<var>fromtext</var>的内容模型。如果未指定,这将基于其他参数猜想。",
"apihelp-compare-param-fromcontentformat": "<var>fromtext</var>的内容序列化格式。",
+ "apihelp-compare-param-fromsection": "只使用指定“from”内容的指定章节。",
"apihelp-compare-param-totitle": "要比较的第二个标题。",
"apihelp-compare-param-toid": "要比较的第二个页面 ID。",
"apihelp-compare-param-torev": "要比较的第二个修订版本。",
"apihelp-compare-param-torelative": "使用与定义自<var>fromtitle</var>、<var>fromid</var>或<var>fromrev</var>的修订版本相关的修订版本。所有其他“to”的选项将被忽略。",
- "apihelp-compare-param-totext": "使用该文本而不是由<var>totitle</var>、<var>toid</var>或<var>torev</var>指定的修订版本内容。",
- "apihelp-compare-param-tosection": "只使用指定“to”内容的指定章节。",
"apihelp-compare-param-topst": "在<var>totext</var>执行预保存转换。",
+ "apihelp-compare-param-totext": "使用该文本而不是由<var>totitle</var>、<var>toid</var>或<var>torev</var>指定的修订版本内容。",
"apihelp-compare-param-tocontentmodel": "<var>totext</var>的内容模型。如果未指定,这将基于其他参数猜想。",
"apihelp-compare-param-tocontentformat": "<var>totext</var>的内容序列化格式。",
+ "apihelp-compare-param-tosection": "只使用指定“to”内容的指定章节。",
"apihelp-compare-param-prop": "要获取的信息束。",
"apihelp-compare-paramvalue-prop-diff": "差异HTML。",
"apihelp-compare-paramvalue-prop-diffsize": "差异HTML的大小(字节)。",
"apihelp-query+redirects-example-generator": "获取所有重定向至[[Main Page]]的信息。",
"apihelp-query+revisions-summary": "获取修订版本信息。",
"apihelp-query+revisions-extended-description": "可用于以下几个方面:\n# 通过设置标题或页面ID获取一批页面(最新修订)的数据。\n# 通过使用带start、end或limit的标题或页面ID获取给定页面的多个修订。\n# 通过revid设置一批修订的ID获取它们的数据。",
- "apihelp-query+revisions-paraminfo-singlepageonly": "å\8f¯è\83½å\8fªè\83½ä¸\8eå\8d\95ä¸\80页é\9d¢使用(模式#2)。",
+ "apihelp-query+revisions-paraminfo-singlepageonly": "å\8fªè\83½å\9c¨å\8d\95ä¸\80页é\9d¢æ¨¡å¼\8fä¸使用(模式#2)。",
"apihelp-query+revisions-param-startid": "从这个修订版本时间戳开始列举。修订版本必须存在,但未必与该页面相关。",
"apihelp-query+revisions-param-endid": "在这个修订版本时间戳停止列举。修订版本必须存在,但未必与该页面相关。",
"apihelp-query+revisions-param-start": "从哪个修订版本时间戳开始列举。",
"apihelp-help-example-query": "兩個查詢子模組的說明。",
"apihelp-imagerotate-summary": "旋轉一張或多張圖片。",
"apihelp-imagerotate-param-rotation": "順時針旋轉圖片的度數。",
+ "apihelp-imagerotate-param-tags": "在更新日誌裡套用到項目的標籤。",
+ "apihelp-imagerotate-example-simple": "<kbd>90</kbd> 度旋轉 <kbd>File:Example.png</kbd>。",
+ "apihelp-imagerotate-example-generator": "<kbd>180</kbd> 度旋轉所有在 <kbd>Category:Flip</kbd> 裡的圖片。",
"apihelp-import-summary": "從其它 wiki 或 XML 檔案來匯入頁面。",
"apihelp-import-param-summary": "匯入摘要。",
"apihelp-import-param-xml": "上載的 XML 檔。",
"apihelp-opensearch-param-suggest": "若<var>[[mw:Special:MyLanguage/Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var>設定為false,則不做任何事。",
"apihelp-opensearch-param-redirects": "如何處理重定向:\n;return:傳回重定向本身。\n;resolve:傳回目標頁面,傳回的結果數目可能少於$1limit。\n由於歷史原因,$1format=json的預設值為「return」,其他格式則為「resolve」。",
"apihelp-opensearch-param-format": "輸出的格式。",
+ "apihelp-opensearch-example-te": "找出以 <kbd>Te</kbd> 為開頭的頁面。",
"apihelp-options-summary": "更改目前使用者的偏好設定。",
"apihelp-options-param-reset": "重設偏好設定為網站預設值。",
"apihelp-options-example-reset": "重設所有偏好設定",
"apihelp-query+alldeletedrevisions-param-user": "此列出由該使用者作出的修訂。",
"apihelp-query+alldeletedrevisions-param-excludeuser": "不要列出由該使用者作出的修訂。",
"apihelp-query+alldeletedrevisions-param-namespace": "僅列出此命名空間的頁面。",
+ "apihelp-query+allfileusages-summary": "列出所有檔案用途,包含不存在的。",
+ "apihelp-query+allfileusages-param-from": "要起始列舉的檔案標題。",
+ "apihelp-query+allfileusages-param-to": "要終止列舉的檔案標題。",
+ "apihelp-query+allfileusages-param-prefix": "搜尋以此值為開頭的所有檔案標題。",
"apihelp-query+allfileusages-param-prop": "要包含到的資訊部份:",
"apihelp-query+allfileusages-paramvalue-prop-title": "添加檔案標題。",
"apihelp-query+allfileusages-param-limit": "要回傳的項目總數。",
"apihelp-query+allimages-param-sha1base36": "以 base 36 的圖片 SHA1 雜湊值(使用在 MediaWiki)。",
"apihelp-query+allimages-param-mime": "所要搜尋的 MIME 類型,例如:<kbd>image/jpeg</kbd>。",
"apihelp-query+allimages-param-limit": "要回傳的圖片總數。",
+ "apihelp-query+allimages-example-B": "搜尋以字母 <kbd>B</kbd> 為開頭的所有檔案清單。",
+ "apihelp-query+allimages-example-recent": "顯示近期已上傳檔案的清單,類似於 [[Special:NewFiles]]。",
"apihelp-query+alllinks-param-from": "要起始列舉的連結標題。",
"apihelp-query+alllinks-param-to": "要終止列舉的連結標題。",
"apihelp-query+alllinks-param-prop": "要包含的資訊部份:",
"apihelp-query+alllinks-param-namespace": "要列舉的命名空間。",
"apihelp-query+alllinks-param-limit": "要回傳的項目總數。",
"apihelp-query+alllinks-param-dir": "列出時所採用的方向。",
+ "apihelp-query+alllinks-example-unique": "列出唯一的連結標題。",
"apihelp-query+alllinks-example-unique-generator": "取得所有已連結標題,標記為遺失。",
"apihelp-query+alllinks-example-generator": "取得包含連結的頁面。",
"apihelp-query+allmessages-summary": "返回來自該網站的訊息。",
"apihelp-query+allusers-param-prop": "要包含的資訊部份:",
"apihelp-query+allusers-paramvalue-prop-rights": "列出使用者所擁有的權限。",
"apihelp-query+allusers-paramvalue-prop-editcount": "添加使用者的編輯次數。",
+ "apihelp-query+allusers-param-witheditsonly": "僅列出有做過編輯的使用者。",
"apihelp-query+allusers-example-Y": "列出以<kbd>Y</kbd>開頭的使用者。",
"apihelp-query+authmanagerinfo-summary": "取得目前身分核對狀態的資訊。",
"apihelp-query+backlinks-summary": "找出連結至指定頁面的所有頁面。",
"apihelp-query+blocks-paramvalue-prop-userid": "添加已封鎖使用者的使用者 ID。",
"apihelp-query+blocks-paramvalue-prop-by": "添加進行封鎖中的使用者之使用者名稱。",
"apihelp-query+blocks-paramvalue-prop-byid": "添加進行封鎖中的使用者之使用者 ID。",
+ "apihelp-query+blocks-paramvalue-prop-reason": "添加封鎖的原因。",
"apihelp-query+blocks-example-simple": "列出封鎖。",
"apihelp-query+blocks-example-users": "列出使用者 <kbd>Alice</kbd> 與 <kbd>Bob</kbd> 的封鎖。",
"apihelp-query+categories-summary": "列出頁面隸屬的所有分類。",
+ "apihelp-query+categories-param-show": "要顯示出的分類種類。",
"apihelp-query+categories-param-limit": "要回傳的分類數量。",
+ "apihelp-query+categories-param-dir": "列出時所採用的方向。",
"apihelp-query+categoryinfo-summary": "回傳有關指定分類的資訊。",
"apihelp-query+categorymembers-summary": "在指定的分類中列出所有頁面。",
+ "apihelp-query+categorymembers-param-prop": "要包含的資訊部份:",
"apihelp-query+categorymembers-paramvalue-prop-ids": "添加頁面 ID。",
+ "apihelp-query+categorymembers-paramvalue-prop-title": "添加標題與頁面的命名空間 ID。",
"apihelp-query+categorymembers-param-limit": "回傳的頁面數量上限。",
"apihelp-query+categorymembers-param-sort": "作為排序順序的屬性。",
"apihelp-query+categorymembers-param-startsortkey": "請改用 $1starthexsortkey。",
"apihelp-query+deletedrevs-param-end": "終止列舉的時間戳記。",
"apihelp-query+deletedrevs-param-from": "在此標題開始列出。",
"apihelp-query+deletedrevs-param-to": "在此標題停止列出。",
+ "apihelp-query+deletedrevs-param-tag": "僅列出以此標籤所標記的修訂。",
"apihelp-query+deletedrevs-param-user": "此列出由該使用者作出的修訂。",
"apihelp-query+deletedrevs-param-excludeuser": "不要列出由該使用者作出的修訂。",
"apihelp-query+deletedrevs-param-namespace": "僅列出此命名空間的頁面。",
"apihelp-query+imageinfo-paramvalue-prop-mime": "替檔案添加 MIME 類型。",
"apihelp-query+imageinfo-paramvalue-prop-mediatype": "添加檔案的媒體類型。",
"apihelp-query+imageinfo-param-limit": "每個檔案要回傳的檔案修訂數量。",
+ "apihelp-query+imageinfo-param-start": "列出的起始時間戳記。",
+ "apihelp-query+imageinfo-param-end": "列出的終止時間戳記。",
+ "apihelp-query+imageinfo-param-urlheight": "與 $1urlwidth 相似。",
"apihelp-query+images-summary": "回傳指定頁面中包含的所有檔案。",
"apihelp-query+images-param-limit": "要回傳的檔案數量。",
"apihelp-query+images-param-dir": "列出時所採用的方向。",
"apihelp-query+images-example-simple": "取得使用在 [[Main Page]] 的檔案清單。",
+ "apihelp-query+imageusage-param-title": "要搜尋的標題。不能與 $1pageid 一起使用。",
+ "apihelp-query+imageusage-param-pageid": "要搜尋的頁面 ID。不能與 $1title 一起使用。",
"apihelp-query+imageusage-param-namespace": "要列舉的命名空間。",
"apihelp-query+imageusage-param-dir": "列出時所採用的方向。",
"apihelp-query+info-summary": "取得基本頁面訊息。",
"apihelp-query+info-param-prop": "要取得的額外屬性:",
"apihelp-query+info-paramvalue-prop-protection": "列出各頁面的保護層級。",
"apihelp-query+info-paramvalue-prop-readable": "使用者是否可閱讀此頁面。",
+ "apihelp-query+iwbacklinks-param-prefix": "跨 wiki 前綴。",
+ "apihelp-query+iwbacklinks-param-limit": "要回傳的頁面總數。",
"apihelp-query+iwbacklinks-param-prop": "要取得的屬性。",
+ "apihelp-query+iwbacklinks-paramvalue-prop-iwprefix": "添加跨 wiki 前綴。",
+ "apihelp-query+iwbacklinks-paramvalue-prop-iwtitle": "添加跨 wiki 標題。",
+ "apihelp-query+iwbacklinks-param-dir": "列出時所採用的方向。",
+ "apihelp-query+iwbacklinks-example-simple": "取得連結至 [[wikibooks:Test]] 的頁面。",
"apihelp-query+iwlinks-summary": "回傳指定頁面的所有 interwiki 連結。",
"apihelp-query+iwlinks-paramvalue-prop-url": "添加完整的 URL。",
"apihelp-query+iwlinks-param-limit": "要回傳的跨 Wiki 連結數量。",
"apihelp-query+iwlinks-param-dir": "列出時所採用的方向。",
+ "apihelp-query+langbacklinks-param-lang": "用於語言的語言連結。",
+ "apihelp-query+langbacklinks-param-title": "要搜尋的語言連結。必須與$1lang一同使用。",
"apihelp-query+langbacklinks-param-limit": "要回傳的頁面總數。",
"apihelp-query+langbacklinks-param-prop": "要取得的屬性。",
+ "apihelp-query+langbacklinks-paramvalue-prop-lllang": "添加用於語言連結的語言代碼。",
+ "apihelp-query+langbacklinks-paramvalue-prop-lltitle": "添加語言連結標題。",
"apihelp-query+langbacklinks-param-dir": "列出時所採用的方向。",
+ "apihelp-query+langbacklinks-example-simple": "取得連結至 [[:fr:Test]] 的頁面。",
"apihelp-query+langlinks-summary": "回傳指定頁面的所有跨語言連結。",
"apihelp-query+langlinks-param-limit": "要回傳的 langlinks 數量。",
"apihelp-query+langlinks-paramvalue-prop-url": "添加完整的 URL。",
+ "apihelp-query+langlinks-paramvalue-prop-autonym": "添加本地語言名稱。",
"apihelp-query+langlinks-param-dir": "列出時所採用的方向。",
"apihelp-query+langlinks-param-inlanguagecode": "用於本地化語言名稱的語言代碼。",
"apihelp-query+links-summary": "回傳指定頁面的所有連結。",
"apihelp-query+links-param-limit": "要回傳的連結數量。",
+ "apihelp-query+links-param-dir": "列出時所採用的方向。",
"apihelp-query+linkshere-param-prop": "要取得的屬性。",
"apihelp-query+linkshere-paramvalue-prop-pageid": "各頁面的頁面 ID。",
"apihelp-query+linkshere-paramvalue-prop-title": "各頁面的標題。",
"apihelp-query+linkshere-param-limit": "要回傳的數量。",
"apihelp-query+logevents-summary": "從日誌中獲取事件。",
"apihelp-query+logevents-param-prop": "要取得的屬性。",
+ "apihelp-query+logevents-paramvalue-prop-ids": "添加日誌事件的 ID。",
"apihelp-query+logevents-param-start": "起始列舉的時間戳記。",
"apihelp-query+logevents-param-end": "結束列舉的時間戳記。",
"apihelp-query+logevents-param-limit": "要回傳的事件項目總數。",
+ "apihelp-query+logevents-example-simple": "列出近期日誌事件。",
"apihelp-query+pagepropnames-param-limit": "回傳的名稱數量上限。",
"apihelp-query+pagepropnames-example-simple": "取得前 10 個屬性名稱。",
"apihelp-query+pageswithprop-paramvalue-prop-ids": "添加頁面 ID。",
+ "apihelp-query+pageswithprop-paramvalue-prop-value": "添加頁面屬性的值。",
"apihelp-query+pageswithprop-param-limit": "回傳的頁面數量上限。",
"apihelp-query+prefixsearch-param-search": "搜尋字串。",
"apihelp-query+prefixsearch-param-namespace": "搜尋的命名空間。若 <var>$1search</var> 以有效的命名空間前綴為開頭則會被忽略。",
"apihelp-query+redirects-paramvalue-prop-title": "各重新導向的標題。",
"apihelp-query+redirects-param-namespace": "僅包含這些命名空間的頁面。",
"apihelp-query+redirects-param-limit": "要回傳的重新導向數量。",
+ "apihelp-query+redirects-example-simple": "取得 [[Main Page]] 的重新導向清單",
"apihelp-query+revisions-summary": "取得修訂的資訊。",
"apihelp-query+revisions-example-content": "取得用於標題 <kbd>API</kbd> 與 <kbd>Main Page</kbd> 最新修訂內容的資料。",
"apihelp-query+revisions-example-last5": "取得 <kbd>Main Page</kbd> 的最近 5 筆修訂。",
"apihelp-query+revisions-example-first5-not-localhost": "取得 <kbd>Main Page</kbd> 裡並非由匿名使用者 <kbd>127.0.0.1</kbd> 所做出的最早前 5 筆修訂。",
"apihelp-query+revisions-example-first5-user": "取得 <kbd>Main Page</kbd> 裡由使用者 <kbd>MediaWiki default</kbd> 所做出的最早前 5 筆修訂。",
"apihelp-query+revisions+base-paramvalue-prop-ids": "修訂 ID。",
+ "apihelp-query+revisions+base-paramvalue-prop-user": "做出修訂的使用者。",
"apihelp-query+revisions+base-paramvalue-prop-tags": "修訂標籤。",
"apihelp-query+search-summary": "執行全文搜尋。",
"apihelp-query+search-param-what": "要執行的搜尋類型。",
"apihelp-query+search-paramvalue-prop-score": "已忽略",
"apihelp-query+search-paramvalue-prop-hasrelated": "已忽略",
"apihelp-query+search-param-limit": "要回傳的頁面總數。",
+ "apihelp-query+search-example-simple": "搜尋 <kbd>meaning</kbd>。",
+ "apihelp-query+search-example-text": "搜尋 <kbd>meaning</kbd> 的文字。",
+ "apihelp-query+siteinfo-param-prop": "要取得的資訊:",
"apihelp-query+siteinfo-paramvalue-prop-general": "全面系統資訊。",
"apihelp-query+siteinfo-paramvalue-prop-specialpagealiases": "特殊頁面別名清單。",
"apihelp-query+siteinfo-param-numberingroup": "列出在使用者群組裡的使用者數目。",
"apihelp-query+siteinfo-example-simple": "索取站台資訊。",
"apihelp-query+siteinfo-example-interwiki": "索取本地端跨 wiki 前綴的清單。",
+ "apihelp-query+siteinfo-example-replag": "檢查目前的響應延遲。",
"apihelp-query+stashimageinfo-summary": "回傳多筆儲藏檔案的檔案資訊。",
"apihelp-query+stashimageinfo-example-simple": "回傳儲藏檔案的檔案資訊。",
"apihelp-query+tags-summary": "列出變更標記。",
"apihelp-query+usercontribs-paramvalue-prop-comment": "添加編輯的註釋。",
"apihelp-query+usercontribs-paramvalue-prop-parsedcomment": "添加編輯的已解析註解。",
"apihelp-query+usercontribs-paramvalue-prop-size": "添加編輯的新大小。",
+ "apihelp-query+usercontribs-paramvalue-prop-tags": "列出編輯的標籤。",
"apihelp-query+userinfo-summary": "取得目前使用者的資訊。",
"apihelp-query+userinfo-param-prop": "要包含的資訊部份:",
"apihelp-query+userinfo-paramvalue-prop-realname": "添加使用者的真實姓名。",
"apihelp-query+users-param-prop": "要包含的資訊部份:",
"apihelp-query+watchlist-param-start": "起始列舉的時間戳記。",
"apihelp-query+watchlist-param-end": "結束列舉的時間戳記。",
+ "apihelp-query+watchlist-param-user": "此列出由該使用者作出的更改。",
+ "apihelp-query+watchlist-param-excludeuser": "不要列出由該使用者作出的更改。",
"apihelp-query+watchlist-param-limit": "每個請求要回傳的結果總數。",
+ "apihelp-query+watchlist-param-prop": "要取得的額外屬性:",
"apihelp-query+watchlist-paramvalue-prop-title": "添加頁面標題。",
"apihelp-query+watchlist-paramvalue-prop-flags": "添加編輯的標籤。",
"apihelp-query+watchlist-paramvalue-prop-tags": "列出項目的標籤。",
+ "apihelp-query+watchlist-paramvalue-type-edit": "一般頁面編輯。",
"apihelp-query+watchlist-paramvalue-type-new": "頁面建立。",
"apihelp-query+watchlist-paramvalue-type-log": "日誌項目。",
"apihelp-query+watchlist-paramvalue-type-categorize": "分類成員更改。",
"apihelp-query+watchlistraw-param-limit": "每個請求要回傳的結果總數。",
+ "apihelp-query+watchlistraw-param-prop": "要取得的額外屬性:",
"apihelp-query+watchlistraw-param-dir": "列出時所採用的方向。",
+ "apihelp-query+watchlistraw-example-simple": "列出在目前使用者的監視清單裡頭頁面。",
"apihelp-removeauthenticationdata-summary": "為目前使用者移除身分核對資料。",
+ "apihelp-resetpassword-summary": "寄送重新設定密碼的電子郵件給使用者。",
"apihelp-revisiondelete-summary": "刪除和取消刪除修訂。",
"apihelp-rollback-summary": "撤修頁面的最後一次編輯。",
"apihelp-setpagelanguage-summary": "更改頁面的語言。",
"apihelp-setpagelanguage-param-reason": "變更的原因。",
"apihelp-stashedit-param-title": "正在編輯此頁面的標題。",
"apihelp-stashedit-param-text": "頁面內容。",
+ "apihelp-tag-param-reason": "變更的原因。",
"apihelp-tokens-summary": "取得資料修改動作的密鑰。",
"apihelp-tokens-extended-description": "此模組已因支援 [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] 而停用。",
"apihelp-unblock-summary": "解除封鎖一位使用者。",
"apihelp-unblock-example-id": "解除封銷 ID #<kbd>105</kbd>。",
"apihelp-undelete-param-title": "要恢復的頁面標題。",
"apihelp-undelete-param-reason": "還原的原因。",
+ "apihelp-undelete-example-page": "取消刪除頁面 <kbd>Main Page</kbd>。",
+ "apihelp-undelete-example-revisions": "取消刪除 <kbd>Main Page</kbd> 的兩筆修訂。",
"apihelp-upload-param-filename": "目標檔案名稱。",
"apihelp-upload-param-comment": "上傳註釋。如果 <var>$1text</var> 未指定的話,也會作為新檔案用的初始頁面文字。",
+ "apihelp-upload-param-text": "用於新檔案的初始頁面文字。",
"apihelp-upload-param-watch": "監視頁面。",
"apihelp-upload-param-ignorewarnings": "忽略所有警告。",
"apihelp-upload-param-file": "檔案內容。",
+ "apihelp-upload-param-url": "索取檔案的來源 URL。",
"apihelp-upload-example-url": "從 URL 上傳。",
"apihelp-userrights-summary": "變更一位使用者的群組成員。",
"apihelp-userrights-param-user": "使用者名稱。",
"apihelp-userrights-param-remove": "從這些群組移除使用者。",
"apihelp-userrights-param-reason": "變更的原因。",
"apihelp-validatepassword-param-password": "要驗證的密碼。",
+ "apihelp-validatepassword-param-email": "電子郵件地址,用於當測試帳號建立時使用。",
+ "apihelp-validatepassword-param-realname": "真實姓名,用於當測試帳號建立時使用。",
"apihelp-watch-example-watch": "監視頁面 <kbd>Main Page</kbd>。",
+ "apihelp-watch-example-unwatch": "取消監視頁面 <kbd>Main Page</kbd>。",
"apihelp-format-example-generic": "以 $1 格式傳回查詢結果。",
"apihelp-json-summary": "使用 JSON 格式輸出資料。",
"apihelp-jsonfm-summary": "使用 JSON 格式輸出資料 (使用 HTML 格式顯示)。",
"apihelp-phpfm-summary": "使用序列化 PHP 格式輸出資料 (使用 HTML 格式顯示)。",
"apihelp-rawfm-summary": "使用 JSON 格式的除錯元素輸出資料 (使用 HTML 格式顯示)。",
"apihelp-xml-summary": "使用 XML 格式輸出資料。",
+ "apihelp-xml-param-includexmlnamespace": "若有指定,添加一個 XML 命名空間。",
"apihelp-xmlfm-summary": "使用 XML 格式輸出資料 (使用 HTML 格式顯示)。",
"api-format-title": "MediaWiki API 結果",
"api-format-prettyprint-header": "這是$1格式的HTML呈現。HTML適合用於除錯,但不適合應用程式使用。\n\n指定<var>format</var>參數以更改輸出格式。要檢視$1格式的非HTML呈現,設定<kbd>format=$2</kbd>。\n\n參考 [[mw:Special:MyLanguage/API|完整說明文件]] 或 [[Special:ApiHelp/main|API說明]] 以取得更多資訊。",
"api-help-title": "MediaWiki API 說明",
"api-help-lead": "此頁為自動產生的 MediaWiki API 說明文件頁面。\n\n說明文件與範例:https://www.mediawiki.org/wiki/API",
"api-help-main-header": "主要模組",
+ "api-help-undocumented-module": "沒有用於模組 $1 的說明文件。",
"api-help-flag-deprecated": "此模組已停用。",
"api-help-flag-internal": "<strong>此模組是內部的或不穩定的。</strong>它的操作可能更改而不另行通知。",
"api-help-flag-readrights": "此模組需要讀取權限。",
"api-help-authmanagerhelper-returnurl": "為第三方身份驗證流程傳回URL,必須為絕對值。需要此值或<var>$1continue</var>兩者之一。\n\n在接收<samp>REDIRECT</samp>回應時,一般狀況下您將打開瀏覽器或網站瀏覽功能到特定的<samp>redirecttarget</samp> URL以進行第三方身份驗證流程。當它完成時,第三方會將瀏覽器或網站瀏覽功能送至此URL。您應當提取任何來自URL的查詢或POST參數,並將之作為<var>$1continue</var>請求傳遞至此API模組。",
"api-help-authmanagerhelper-continue": "此請求是在先前的<samp>UI</samp>或<samp>REDIRECT</samp>回應之後的後續動作。必須為此值或<var>$1returnurl</var>。",
"api-help-authmanagerhelper-additional-params": "此模組允許額外參數,取決於可用的身份驗證請求。使用<kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd>与<kbd>amirequestsfor=$1</kbd>(或之前來自此模組的回應,如果合適)以決定可用請求及其使用的欄位。",
+ "apierror-badgenerator-unknown": "未知的 <kbd>generator=$1</kbd>。",
"apierror-badip": "IP 參數無效。",
"apierror-badmd5": "提供的 MD5 雜湊不正確。",
"apierror-badquery": "無效的查詢。",
+ "apierror-cantblock": "您沒有權限來解封使用者。",
+ "apierror-cantimport": "您沒有權限來匯入頁面。",
+ "apierror-changeauth-norequest": "建立更改請求失敗。",
+ "apierror-contentserializationexception": "內容序列化失敗:$1",
"apierror-copyuploadbadurl": "不允許從此 URL 來上傳。",
+ "apierror-csp-report": "處理 CSP 報告時錯誤:$1。",
"apierror-filedoesnotexist": "檔案不存在。",
"apierror-filenopath": "無法取得本地端檔案路徑。",
"apierror-filetypecannotberotated": "無法旋轉的檔案類型。",
"apierror-imageusage-badtitle": "<kbd>$1</kbd>的標題必須是檔案。",
"apierror-import-unknownerror": "未知的匯入錯誤:$1",
"apierror-invalidsha1hash": "所提供的 SHA1 雜湊無效。",
+ "apierror-invalidtitle": "錯誤標題「$1」。",
"apierror-invaliduser": "無效的使用者名稱「$1」。",
"apierror-invaliduserid": "使用者 ID <var>$1</var> 無效。",
"apierror-missingparam": "<var>$1</var>參數必須被設定。",
"apierror-mustbeloggedin-generic": "您必須登入。",
"apierror-mustbeloggedin-linkaccounts": "您必須登入到連結帳號。",
"apierror-mustbeloggedin-removeauth": "必須登入,才能移除身分核對資取。",
+ "apierror-mustbeloggedin": "您必須登入至$1。",
"apierror-nodeleteablefile": "沒有這樣檔案的舊版本。",
"apierror-noedit-anon": "匿名使用者不可編輯頁面。",
"apierror-noedit": "您沒有權限來編輯頁面。",
"apierror-permissiondenied": "您沒有權限$1。",
"apierror-permissiondenied-generic": "權限不足。",
"apierror-permissiondenied-unblock": "您沒有權限來解封使用者。",
+ "apierror-protect-invalidaction": "無效的保護類型「$1」。",
+ "apierror-protect-invalidlevel": "無效的保護層級「$1」。",
"apierror-readapidenied": "您需要有閱讀權限來使用此模組。",
"apierror-readonly": "Wiki 目前為唯讀模式。",
"apierror-reauthenticate": "於本工作階段還未核對身分,請重新核對。",
* @file
*/
+use Wikimedia\StaticArrayWriter;
+
/**
* @since 1.26
*/
}
if ( is_array( $value ) ) {
// [A]rray
- return [ 'a', array_map( function ( $v ) {
- return LCStoreStaticArray::encode( $v );
- }, $value ) ];
+ return [ 'a', array_map( 'LCStoreStaticArray::encode', $value ) ];
}
throw new RuntimeException( 'Cannot encode ' . var_export( $value, true ) );
case 's':
return unserialize( $data );
case 'a':
- return array_map( function ( $v ) {
- return LCStoreStaticArray::decode( $v );
- }, $data );
+ return array_map( 'LCStoreStaticArray::decode', $data );
default:
throw new RuntimeException(
'Unable to decode ' . var_export( $encoded, true ) );
}
public function finishWrite() {
- file_put_contents(
- $this->fname,
- "<?php\n" .
- "// Generated by LCStoreStaticArray.php -- do not edit!\n" .
- "return " .
- var_export( $this->data[$this->currentLang], true ) . ';'
+ $writer = new StaticArrayWriter();
+ $out = $writer->create(
+ $this->data[$this->currentLang],
+ 'Generated by LCStoreStaticArray.php -- do not edit!'
);
+ file_put_contents( $this->fname, $out );
$this->currentLang = null;
$this->fname = null;
}
<?php
-
-use MediaWiki\Logger\LoggerFactory;
-use MediaWiki\MediaWikiServices;
-use MediaWiki\Search\ParserOutputSearchDataExtractor;
-
/**
* Base class for content handling.
*
*
* @author Daniel Kinzler
*/
+
+use Wikimedia\Assert\Assert;
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Search\ParserOutputSearchDataExtractor;
+
/**
* A content handler knows how do deal with a specific type of content on a wiki
* page. Content is stored in the database in a serialized form (using a
$differenceEngine = $this->createDifferenceEngine( $context );
if ( get_class( $differenceEngine ) !== DifferenceEngine::class ) {
// TODO turn this into a deprecation warning in a later release
- LoggerFactory::getInstance( 'diff' )->notice(
+ LoggerFactory::getInstance( 'diff' )->info(
'Falling back to DifferenceEngineSlotDiffRenderer', [
'modelID' => $this->getModelID(),
'DifferenceEngine' => get_class( $differenceEngine ),
* must exist and must not be deleted.
*
* @since 1.21
+ * @since 1.32 accepts Content objects for all parameters instead of Revision objects.
+ * Passing Revision objects is deprecated.
*
- * @param Revision $current The current text
- * @param Revision $undo The revision to undo
- * @param Revision $undoafter Must be an earlier revision than $undo
+ * @param Revision|Content $current The current text
+ * @param Revision|Content $undo The content of the revision to undo
+ * @param Revision|Content $undoafter Must be from an earlier revision than $undo
+ * @param bool $undoIsLatest Set true if $undo is from the current revision (since 1.32)
*
* @return mixed Content on success, false on failure
*/
- public function getUndoContent( Revision $current, Revision $undo, Revision $undoafter ) {
- $cur_content = $current->getContent();
+ public function getUndoContent( $current, $undo, $undoafter, $undoIsLatest = false ) {
+ Assert::parameterType( Revision::class . '|' . Content::class, $current, '$current' );
+ if ( $current instanceof Content ) {
+ Assert::parameter( $undo instanceof Content, '$undo',
+ 'Must be Content when $current is Content' );
+ Assert::parameter( $undoafter instanceof Content, '$undoafter',
+ 'Must be Content when $current is Content' );
+ $cur_content = $current;
+ $undo_content = $undo;
+ $undoafter_content = $undoafter;
+ } else {
+ Assert::parameter( $undo instanceof Revision, '$undo',
+ 'Must be Revision when $current is Revision' );
+ Assert::parameter( $undoafter instanceof Revision, '$undoafter',
+ 'Must be Revision when $current is Revision' );
- if ( empty( $cur_content ) ) {
- return false; // no page
- }
+ $cur_content = $current->getContent();
- $undo_content = $undo->getContent();
- $undoafter_content = $undoafter->getContent();
+ if ( empty( $cur_content ) ) {
+ return false; // no page
+ }
+
+ $undo_content = $undo->getContent();
+ $undoafter_content = $undoafter->getContent();
+
+ if ( !$undo_content || !$undoafter_content ) {
+ return false; // no content to undo
+ }
- if ( !$undo_content || !$undoafter_content ) {
- return false; // no content to undo
+ $undoIsLatest = $current->getId() === $undo->getId();
}
try {
$this->checkModelID( $cur_content->getModel() );
$this->checkModelID( $undo_content->getModel() );
- if ( $current->getId() !== $undo->getId() ) {
+ if ( !$undoIsLatest ) {
// If we are undoing the most recent revision,
// its ok to revert content model changes. However
// if we are undoing a revision in the middle, then
public function msg( $key ) {
$args = func_get_args();
+ // phpcs:ignore MediaWiki.Usage.ExtendClassUsage.FunctionVarUsage
return wfMessage( ...$args )->setContext( $this );
}
}
public function __construct( IMaintainableDatabase $db, array $tablesToClone,
$newTablePrefix, $oldTablePrefix = null, $dropCurrentTables = true
) {
+ if ( !$tablesToClone ) {
+ throw new InvalidArgumentException( 'Empty list of tables to clone' );
+ }
$this->db = $db;
$this->tablesToClone = $tablesToClone;
$this->newTablePrefix = $newTablePrefix;
*/
const DIFF_VERSION = '1.12';
- /** @var int Revision ID or 0 for current */
+ /**
+ * Revision ID for the old revision. 0 for the revision previous to $mNewid, false
+ * if the diff does not have an old revision (e.g. 'oldid=<first revision of page>&diff=prev'),
+ * or the revision does not exist, null if the revision is unsaved.
+ * @var int|false|null
+ */
protected $mOldid;
- /** @var int|string Revision ID or null for current or an alias such as 'next' */
+ /**
+ * Revision ID for the new revision. 0 for the last revision of the current page
+ * (as defined by the request context), false if the revision does not exist, null
+ * if it is unsaved, or an alias such as 'next'.
+ * @var int|string|false|null
+ */
protected $mNewid;
- private $mOldTags;
- private $mNewTags;
-
/**
* Old revision (left pane).
* Allowed to be an unsaved revision, unlikely that's ever needed though.
- * Null when the old revision does not exist; this can happen when using
- * diff=prev on the first revision.
+ * False when the old revision does not exist; this can happen when using
+ * diff=prev on the first revision. Null when the revision should exist but
+ * doesn't (e.g. load failure); loadRevisionData() will return false in that
+ * case. Also null until lazy-loaded. Ignored completely when isContentOverridden
+ * is set.
* Since 1.32 public access is deprecated.
- * @var Revision|null
+ * @var Revision|null|false
*/
protected $mOldRev;
/**
* New revision (right pane).
* Note that this might be an unsaved revision (e.g. for edit preview).
- * Null only in case of load failure; diff methods will just return an error message in that case.
+ * Null in case of load failure; diff methods will just return an error message in that case,
+ * and loadRevisionData() will return false. Also null until lazy-loaded. Ignored completely
+ * when isContentOverridden is set.
* Since 1.32 public access is deprecated.
* @var Revision|null
*/
*/
protected $mNewPage;
+ /**
+ * Change tags of $mOldRev or null if it does not exist / is not saved.
+ * @var string[]|null
+ */
+ private $mOldTags;
+
+ /**
+ * Change tags of $mNewRev or null if it does not exist / is not saved.
+ * @var string[]|null
+ */
+ private $mNewTags;
+
/**
* @var Content|null
* @deprecated since 1.32, content slots are now handled by the corresponding SlotDiffRenderer.
/**
* Get the old and new content objects for all slots.
* This method does not do any permission checks.
- * @return array [ role => [ 'old' => SlotRecord, 'new' => SlotRecord ], ... ]
+ * @return array [ role => [ 'old' => SlotRecord|null, 'new' => SlotRecord|null ], ... ]
*/
protected function getSlotContents() {
if ( $this->isContentOverridden ) {
'new' => $this->mNewContent,
]
];
+ } elseif ( !$this->loadRevisionData() ) {
+ return [];
}
- $oldRev = $this->mOldRev->getRevisionRecord();
- $newRev = $this->mNewRev->getRevisionRecord();
+ $newSlots = $this->mNewRev->getRevisionRecord()->getSlots()->getSlots();
+ if ( $this->mOldRev ) {
+ $oldSlots = $this->mOldRev->getRevisionRecord()->getSlots()->getSlots();
+ } else {
+ $oldSlots = [];
+ }
// The order here will determine the visual order of the diff. The current logic is
- // changed first, then added, then deleted. This is ad hoc and should not be relied on
- // - in the future we may want the ordering to depend on the page type.
- $roles = array_merge( $newRev->getSlotRoles(), $oldRev->getSlotRoles() );
- $oldSlots = $oldRev->getSlots()->getSlots();
- $newSlots = $newRev->getSlots()->getSlots();
+ // slots of the new revision first in natural order, then deleted ones. This is ad hoc
+ // and should not be relied on - in the future we may want the ordering to depend
+ // on the page type.
+ $roles = array_merge( array_keys( $newSlots ), array_keys( $oldSlots ) );
$slots = [];
foreach ( $roles as $role ) {
}
/**
- * @return int
+ * Get the ID of old revision (left pane) of the diff. 0 for the revision
+ * previous to getNewid(), false if the old revision does not exist, null
+ * if it's unsaved.
+ * To get a real revision ID instead of 0, call loadRevisionData() first.
+ * @return int|false|null
*/
public function getOldid() {
$this->loadRevisionIds();
}
/**
- * @return bool|int
+ * Get the ID of new revision (right pane) of the diff. 0 for the current revision,
+ * false if the new revision does not exist, null if it's unsaved.
+ * To get a real revision ID instead of 0, call loadRevisionData() first.
+ * @return int|false|null
*/
public function getNewid() {
$this->loadRevisionIds();
return $difftext;
}
+ /**
+ * Get the diff table body for one slot, without header
+ *
+ * @param string $role
+ * @return string|false
+ */
+ public function getDiffBodyForRole( $role ) {
+ $diffRenderers = $this->getSlotDiffRenderers();
+ if ( !isset( $diffRenderers[$role] ) ) {
+ return false;
+ }
+
+ $slotContents = $this->getSlotContents();
+ $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role]['old'],
+ $slotContents[$role]['new'] );
+ if ( !$slotDiff ) {
+ return false;
+ }
+
+ if ( $role !== 'main' ) {
+ // TODO use human-readable role name at least
+ $slotTitle = $role;
+ $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff;
+ }
+
+ return $this->localiseDiff( $slotDiff );
+ }
+
/**
* Get a slot header for inclusion in a diff body (as a table row).
*
$this->mOldContent = $oldRevision ? $oldRevision->getContent( 'main',
RevisionRecord::FOR_THIS_USER, $this->getUser() ) : null;
} else {
- $this->mOldRev = $this->mOldid = $this->mOldPage = null;
+ $this->mOldPage = null;
+ $this->mOldRev = $this->mOldid = false;
}
$this->mNewRev = new Revision( $newRevision );
$this->mNewid = $newRevision->getId();
* @param int $old Revision id, e.g. from URL parameter 'oldid'
* @param int|string $new Revision id or strings 'next' or 'prev', e.g. from URL parameter 'diff'
*
- * @return int[] List of two revision ids, older first, later second.
+ * @return array List of two revision ids, older first, later second.
* Zero signifies invalid argument passed.
* false signifies that there is no previous/next revision ($old is the oldest/newest one).
*/
}
/**
- * Load revision metadata for the specified articles. If newid is 0, then compare
- * the old article in oldid to the current article; if oldid is 0, then
- * compare the current article to the immediately previous one (ignoring the
- * value of newid).
+ * Load revision metadata for the specified revisions. If newid is 0, then compare
+ * the old revision in oldid to the current revision of the current page (as defined
+ * by the request context); if oldid is 0, then compare the revision in newid to the
+ * immediately previous one.
*
* If oldid is false, leave the corresponding revision object set
- * to false. This is impossible via ordinary user input, and is provided for
- * API convenience.
+ * to false. This can happen with 'diff=prev' pointing to a non-existent revision,
+ * and is also used directly by the API.
*
- * @return bool Whether both revisions were loaded successfully.
+ * @return bool Whether both revisions were loaded successfully. Setting mOldRev
+ * to false counts as successful loading.
*/
public function loadRevisionData() {
if ( $this->mRevisionsLoaded ) {
- return $this->isContentOverridden || $this->mNewRev && $this->mOldRev;
+ return $this->isContentOverridden || $this->mNewRev && !is_null( $this->mOldRev );
}
// Whether it succeeds or fails, we don't want to try again
/**
* Load the text of the revisions, as well as revision data.
+ * When the old revision is missing (mOldRev is false), loading mOldContent is not attempted.
*
* @return bool Whether the content of both revisions could be loaded successfully.
+ * (When mOldRev is false, that still counts as a success.)
+ *
*/
public function loadText() {
if ( $this->mTextLoaded == 2 ) {
- return $this->loadRevisionData() && $this->mOldContent && $this->mNewContent;
+ return $this->loadRevisionData() && ( $this->mOldRev === false || $this->mOldContent )
+ && $this->mNewContent;
}
// Whether it succeeds or fails, we don't want to try again
}
}
- if ( $this->mNewRev ) {
- $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
- Hooks::run( 'DifferenceEngineLoadTextAfterNewContentIsLoaded', [ $this ] );
- if ( $this->mNewContent === null ) {
- return false;
- }
+ $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+ Hooks::run( 'DifferenceEngineLoadTextAfterNewContentIsLoaded', [ $this ] );
+ if ( $this->mNewContent === null ) {
+ return false;
}
return true;
$wikidiff2Version = phpversion( 'wikidiff2' );
if (
$wikidiff2Version !== false &&
- version_compare( $wikidiff2Version, '1.5.0', '>=' )
+ version_compare( $wikidiff2Version, '1.5.0', '>=' ) &&
+ version_compare( $wikidiff2Version, '1.8.0', '<' )
) {
$text = wikidiff2_do_diff(
$oldText,
$this->wikiDiff2MovedParagraphDetectionCutoff
);
} else {
- // Don't pass the 4th parameter for compatibility with older versions of wikidiff2
+ // Don't pass the 4th parameter introduced in version 1.5.0 and removed in version 1.8.0
$text = wikidiff2_do_diff(
$oldText,
$newText,
* 'cssclass' -- CSS class
* 'csshelpclass' -- CSS class used to style help text
* 'dir' -- Direction of the element.
- * 'options' -- associative array mapping labels to values.
+ * 'options' -- associative array mapping raw text labels to values.
* Some field types support multi-level arrays.
+ * Overwrites 'options-message'.
* 'options-messages' -- associative array mapping message keys to values.
* Some field types support multi-level arrays.
+ * Overwrites 'options' and 'options-message'.
* 'options-message' -- message key or object to be parsed to extract the list of
* options (like 'ipbreason-dropdown').
* 'label-message' -- message key or object for a message to use as the label.
$thisAttribs['class'] = 'checkmatrix-forced checkmatrix-forced-on';
}
- $checkbox = $this->getOneCheckbox( $checked, $attribs + $thisAttribs );
+ $checkbox = $this->getOneCheckboxHTML( $checked, $attribs + $thisAttribs );
$rowContents .= Html::rawElement(
'td',
return $html;
}
- protected function getOneCheckbox( $checked, $attribs ) {
- if ( $this->mParent instanceof OOUIHTMLForm ) {
- return new OOUI\CheckboxInputWidget( [
- 'name' => "{$this->mName}[]",
- 'selected' => $checked,
- ] + OOUI\Element::configFromHtmlAttributes(
- $attribs
- ) );
- } else {
- $checkbox = Xml::check( "{$this->mName}[]", $checked, $attribs );
- if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
- $checkbox = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) .
- $checkbox .
- Html::element( 'label', [ 'for' => $attribs['id'] ] ) .
- Html::closeElement( 'div' );
- }
- return $checkbox;
+ public function getInputOOUI( $value ) {
+ $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] );
+
+ return new MediaWiki\Widget\CheckMatrixWidget(
+ [
+ 'name' => $this->mName,
+ 'infusable' => true,
+ 'id' => $this->mID,
+ 'rows' => $this->mParams['rows'],
+ 'columns' => $this->mParams['columns'],
+ 'tooltips' => $this->mParams['tooltips'],
+ 'forcedOff' => isset( $this->mParams['force-options-off'] ) ?
+ $this->mParams['force-options-off'] : [],
+ 'forcedOn' => isset( $this->mParams['force-options-on'] ) ?
+ $this->mParams['force-options-on'] : [],
+ 'values' => $value
+ ] + OOUI\Element::configFromHtmlAttributes( $attribs )
+ );
+ }
+
+ protected function getOneCheckboxHTML( $checked, $attribs ) {
+ $checkbox = Xml::check( "{$this->mName}[]", $checked, $attribs );
+ if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+ $checkbox = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) .
+ $checkbox .
+ Html::element( 'label', [ 'for' => $attribs['id'] ] ) .
+ Html::closeElement( 'div' );
}
+ return $checkbox;
}
protected function isTagForcedOff( $tag ) {
return $res;
}
+
+ protected function getOOUIModules() {
+ return [ 'mediawiki.widgets.CheckMatrixWidget' ];
+ }
+
+ protected function shouldInfuseOOUI() {
+ return true;
+ }
}
/** @var Database $conn */
$conn = $status->value;
$dbName = $this->getVar( 'wgDBname' );
- if ( !$conn->selectDB( $dbName ) ) {
+ if ( !$this->databaseExists( $dbName ) ) {
$conn->query(
"CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ) . "CHARACTER SET utf8",
__METHOD__
);
- $conn->selectDB( $dbName );
}
+ $conn->selectDB( $dbName );
$this->setupSchemaVars();
return $status;
}
+ /**
+ * Try to see if a given database exists
+ * @param string $dbName Database name to check
+ * @return bool
+ */
+ private function databaseExists( $dbName ) {
+ $encDatabase = $this->db->addQuotes( $dbName );
+
+ return $this->db->query(
+ "SELECT 1 FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = $encDatabase",
+ __METHOD__
+ )->numRows() > 0;
+ }
+
/**
* @return Status
*/
"config-email-watchlist": "Włącz powiadomienie o zmianach stron obserwowanych",
"config-email-watchlist-help": "Pozwól użytkownikom otrzymywać powiadomienia o zmianach na stronach obserwowanych, jeśli będą mieć włączoną tę funkcję w swoich preferencjach.",
"config-email-auth": "Włącz uwierzytelnianie e‐mailem",
- "config-email-auth-help": "Jeśli ta opcja jest włączona, użytkownicy będą musieli potwierdzić swoje adresy e-mail przy użyciu wysłanego do nich łącza, gdy będą je ustawiać lub zmieniać.\nTylko uwierzytelnione adresy e-mail mogą otrzymywać wiadomości od innych użytkowników lub mailowe powiadomienia o zmianach.\nUstawienie tej opcji jest'''zalecane''' na publicznych wiki ze względu na potencjalne nadużycia funkcji poczty e-mail.",
+ "config-email-auth-help": "Jeśli ta opcja jest włączona, użytkownicy będą musieli potwierdzić swoje adresy e-mail przy użyciu wysłanego do nich łącza, gdy będą je ustawiać lub zmieniać.\nTylko uwierzytelnione adresy e-mail mogą otrzymywać wiadomości od innych użytkowników lub mailowe powiadomienia o zmianach.\nUstawienie tej opcji jest <strong>zalecane</strong> na publicznych wiki ze względu na potencjalne nadużycia funkcji poczty e-mail.",
"config-email-sender": "Zwrotny adres e‐mail",
- "config-email-sender-help": "Wprowadź adres e-mail używany jako adres zwrotny wiadomości wychodzących.\nTo tam będą wysyłane szturchnięcia.\nWiele serwerów poczty wymaga, by co najmniej część nazwy domeny była prawidłowa.",
+ "config-email-sender-help": "Wprowadź adres e-mail używany jako adres zwrotny wiadomości wychodzących.\nTo tam będą wysyłane zwroty z serwerów pocztowych.\nWiele serwerów poczty wymaga, by co najmniej część nazwy domeny była prawidłowa.",
"config-upload-settings": "Przesyłanie obrazków i plików",
"config-upload-enable": "Włącz przesyłanie plików na serwer",
"config-upload-help": "Przesyłanie plików potencjalnie wystawia serwer na zagrożenia.\nWięcej informacji na ten temat można znaleźć w [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security sekcji zabezpieczeń] podręcznika.\n\nAby włączyć przesyłanie plików, zmień właściwości podkatalogu <code>images</code> katalogu głównego MediaWiki tak, aby serwer sieci web mógł zapisywać do niego.\nNastępnie włącz tę opcję.",
"Elftrkn",
"Vito Genovese",
"Incelemeelemani",
- "Hedda"
+ "Hedda",
+ "By erdo can"
]
},
"config-desc": "MediaWiki yükleyicisi",
"config-using-uri": "Sunucu URLsi olarak \"<nowiki>$1$2</nowiki>\" kullanılıyor.",
"config-uploads-not-safe": "<strong>Uyarı:</strong> Yüklemeler için varsayılan dizininiz <code>$1</code>, rastgele komut dosyalarının yürütülmesine karşı savunmasızdır.\nMediaWiki, karşıya yüklenen tüm dosyaları güvenlik tehditlerine karşı denetlese de, yüklemeleri etkinleştirmeden önce [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security bu güvenlik açığını kapatmanız] önemle tavsiye edilir.",
"config-no-cli-uploads-check": "<strong>Uyarı:</strong> Yüklemeler için varsayılan dizininiz (<code>$1</code>), CLI yüklemesi sırasında rastgele kod yürütme güvenlik açığı açısından denetlenmez.",
+ "config-brokenlibxml": "Sisteminizde, \"buggy\" olan ve MediaWiki ve diğer web uygulamalarında gizli veri bozulmasına neden olabilecek PHP ve libxml2 sürümlerinin bir kombinasyonu vardır.\nLibxml2 2.7.3 veya sonraki bir sürüme yükseltin ([https://bugs.php.net/bug.php?id=45996 PHP ile dosyalanmış hata]).\nKurulum iptal edildi.",
"config-db-type": "Veritabanı tipi:",
"config-db-host": "Veritabanı sunucusu:",
"config-db-host-help": "Veritabanı sunucunuz farklı bir sunucu üzerinde ise, ana bilgisayar adını veya IP adresini buraya girin.\n\nPaylaşılan ağ barındırma hizmeti kullanıyorsanız, barındırma sağlayıcınız size doğru bir ana bilgisayar adını kendi belgelerinde vermiştir.\n\nEğer MySQL kullanan bir Windows sunucusuna yükleme yapıyorsanız, sunucu adı olarak \"localhost\" kullanırsanız çalışmayabilir. Çalışmazsa, yerel IP adresi için \"127.0.0.1\" deneyin.\n\nPostgreSQL kullanıyorsanız, bu alanı bir Unix soketi ile bağlanmak için boş bırakın.",
"config-extension-link": "Vikinizin [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions eklentileri] desteklediğini biliyor musunuz?\n\n[https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category Eklentileri kategorilerine göre] inceleyebilir ya da tüm eklentilerin listesini görmek için [https://www.mediawiki.org/wiki/Extension_Matrix Eklenti Matrisine] bakabilirsiniz.",
"config-skins-screenshots": "$1 (ekran görüntüleri: $2)",
"config-screenshot": "ekran görüntüsü",
- "mainpagetext": "'''MediaWiki başarı ile kuruldu.'''",
- "mainpagedocfooter": "Viki yazılımının kullanımı hakkında bilgi almak için [https://meta.wikimedia.org/wiki/Help:Contents kullanıcı rehberine] bakınız.\n\n== Yeni Başlayanlar ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Yapılandırma ayarlarının listesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki SSS]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-posta listesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Kendi diliniz için MediaWiki yerelleştirmesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Kendi vikinizde spam ile nasıl savaşılacağını öğrennin]"
+ "mainpagetext": "<strong>MediaWiki başarı ile kuruldu.</strong>",
+ "mainpagedocfooter": "Viki yazılımının kullanımı hakkında bilgi almak için [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents kullanıcı rehberine] bakınız.\n\n== Yeni Başlayanlar ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Yapılandırma ayarlarının listesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki SSS]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-posta listesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Kendi diliniz için MediaWiki yerelleştirmesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Kendi vikinizde spam ile nasıl savaşılacağını öğrennin]"
}
$lookAhead = ( $idx + 1 < $maxLen ) ? $str[$idx + 1] : '';
$lookBehind = ( $idx - 1 >= 0 ) ? $str[$idx - 1] : '';
if ( $inString ) {
- continue;
+ break;
} elseif ( !$inComment &&
( $lookAhead === '/' || $lookAhead === '*' )
public static function extractUInt32( $string ) {
return unpack( 'V', $string )[1];
}
-};
+}
class StaticArrayWriter {
/**
- * @param string[] $data Array with string keys/values to export
+ * @param array $data Array with keys/values to export
* @param string $header
*
* @return string PHP code
*/
public function create( array $data, $header = 'Automatically generated' ) {
- $format = "\t%s => %s,\n";
$code = "<?php\n"
. "// " . implode( "\n// ", explode( "\n", $header ) ) . "\n"
. "return [\n";
foreach ( $data as $key => $value ) {
- $code .= sprintf(
- $format,
- var_export( $key, true ),
- var_export( $value, true )
- );
+ $code .= $this->encode( $key, $value, 1 );
}
$code .= "];\n";
return $code;
}
+
+ /**
+ * Recursively turn one k/v pair into properly-indented PHP
+ *
+ * @param string|int $key
+ * @param array|mixed $value
+ * @param int $indent Indentation level
+ *
+ * @return string
+ */
+ private function encode( $key, $value, $indent ) {
+ $tabs = str_repeat( "\t", $indent );
+ $line = $tabs .
+ var_export( $key, true ) .
+ ' => ';
+ if ( is_array( $value ) ) {
+ $line .= "[\n";
+ foreach ( $value as $key2 => $value2 ) {
+ $line .= $this->encode( $key2, $value2, $indent + 1 );
+ }
+ $line .= "$tabs]";
+ } else {
+ $line .= var_export( $value, true );
+ }
+
+ $line .= ",\n";
+ return $line;
+ }
}
if ( !is_string( $tmpDirectory ) ) {
$tmpDirectory = self::getUsableTempDirectory();
}
- $path = wfTempDir() . '/' . $prefix . $hex . $ext;
+ $path = $tmpDirectory . '/' . $prefix . $hex . $ext;
Wikimedia\suppressWarnings();
$newFileHandle = fopen( $path, 'x' );
Wikimedia\restoreWarnings();
/**
* Multi-datacenter aware caching interface
*
+ * ### Using WANObjectCache
+ *
* All operations go to the local datacenter cache, except for delete(),
* touchCheckKey(), and resetCheckKey(), which broadcast to all datacenters.
*
* The preferred way to do this logic is through getWithSetCallback().
* When querying the store on cache miss, the closest DB replica
* should be used. Try to avoid heavyweight DB master or quorum reads.
- * When the source data changes, a purge method should be called.
- * Since purges are expensive, they should be avoided. One can do so if:
- * - a) The object cached is immutable; or
- * - b) Validity is checked against the source after get(); or
- * - c) Using a modest TTL is reasonably correct and performant
*
+ * To ensure consumers of the cache see new values in a timely manner,
+ * you either need to follow either the validation strategy, or the
+ * purge strategy.
+ *
+ * The validation strategy refers to the natural avoidance of stale data
+ * by one of the following means:
+ *
+ * - A) The cached value is immutable.
+ * If the consumer has access to an identifier that uniquely describes a value,
+ * cached value need not change. Instead, the key can change. This also allows
+ * all servers to access their perceived current version. This is important
+ * in context of multiple deployed versions of your application and/or cross-dc
+ * database replication, to ensure deterministic values without oscillation.
+ * - B) Validity is checked against the source after get().
+ * This is the inverse of A. The unique identifier is embedded inside the value
+ * and validated after on retreival. If outdated, the value is recomputed.
+ * - C) The value is cached with a modest TTL (without validation).
+ * If value recomputation is reasonably performant, and the value is allowed to
+ * be stale, one should consider using TTL only – using the value's age as
+ * method of validation.
+ *
+ * The purge strategy refers to the the approach whereby your application knows that
+ * source data has changed and can react by purging the relevant cache keys.
+ * As purges are expensive, this strategy should be avoided if possible.
* The simplest purge method is delete().
*
- * There are three supported ways to handle broadcasted operations:
- * - a) Configure the 'purge' EventRelayer to point to a valid PubSub endpoint
- * that has subscribed listeners on the cache servers applying the cache updates.
- * - b) Ommit the 'purge' EventRelayer parameter and set up mcrouter as the underlying cache
+ * No matter which strategy you choose, callers must not rely on updates or purges
+ * being immediately visible to other servers. It should be treated similarly as
+ * one would a database replica.
+ *
+ * The need for immediate updates should be avoided. If needed, solutions must be
+ * sought outside WANObjectCache.
+ *
+ * ### Deploying WANObjectCache
+ *
+ * There are three supported ways to set up broadcasted operations:
+ *
+ * - A) Configure the 'purge' EventRelayer to point to a valid PubSub endpoint
+ * that has subscribed listeners on the cache servers applying the cache updates.
+ * - B) Omit the 'purge' EventRelayer parameter and set up mcrouter as the underlying cache
* backend, using a memcached BagOStuff class for the 'cache' parameter. The 'region'
- * and 'cluster' parameters must be provided and 'mcrouterAware' must be set to 'true'.
+ * and 'cluster' parameters must be provided and 'mcrouterAware' must be set to `true`.
* Configure mcrouter as follows:
* - 1) Use Route Prefixing based on region (datacenter) and cache cluster.
- * See https://github.com/facebook/mcrouter/wiki/Routing-Prefix and
- * https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup
+ * See https://github.com/facebook/mcrouter/wiki/Routing-Prefix and
+ * https://github.com/facebook/mcrouter/wiki/Multi-cluster-broadcast-setup.
* - 2) To increase the consistency of delete() and touchCheckKey() during cache
- * server membership changes, you can use the OperationSelectorRoute to
- * configure 'set' and 'delete' operations to go to all servers in the cache
- * cluster, instead of just one server determined by hashing.
- * See https://github.com/facebook/mcrouter/wiki/List-of-Route-Handles
- * - c) Ommit the 'purge' EventRelayer parameter and set up dynomite as cache middleware
- * between the web servers and either memcached or redis. This will also broadcast all
- * key setting operations, not just purges, which can be useful for cache warming.
- * Writes are eventually consistent via the Dynamo replication model.
- * See https://github.com/Netflix/dynomite
+ * server membership changes, you can use the OperationSelectorRoute to
+ * configure 'set' and 'delete' operations to go to all servers in the cache
+ * cluster, instead of just one server determined by hashing.
+ * See https://github.com/facebook/mcrouter/wiki/List-of-Route-Handles.
+ * - C) Omit the 'purge' EventRelayer parameter and set up dynomite as cache middleware
+ * between the web servers and either memcached or redis. This will broadcast all
+ * key setting operations, not just purges, which can be useful for cache warming.
+ * Writes are eventually consistent via the Dynamo replication model.
+ * See https://github.com/Netflix/dynomite.
*
* Broadcasted operations like delete() and touchCheckKey() are done asynchronously
* in all datacenters this way, though the local one should likely be near immediate.
* @since 1.28
*/
public static function newFromConnection( IDatabase $db, array $params = [] ) {
- return new static( [ 'connection' => $db ] + $params );
+ return new static( array_merge(
+ [ 'localDomain' => $db->getDomainID() ],
+ $params,
+ [ 'connection' => $db ]
+ ) );
}
/**
$server = $this->servers[$i];
$server['serverIndex'] = $i;
$server['autoCommitOnly'] = $autoCommit;
- if ( $this->localDomain->getDatabase() !== null ) {
- // Use the local domain table prefix if the local domain is specified
- $server['tablePrefix'] = $this->localDomain->getTablePrefix();
- }
$conn = $this->reallyOpenConnection( $server, $this->localDomain );
$host = $this->getServerName( $i );
if ( $conn->isOpen() ) {
$this->errorConnection = $conn;
$conn = false;
} else {
- $conn->tablePrefix( $prefix ); // as specified
// Note that if $domain is an empty string, getDomainID() might not match it
$this->conns[$connInUseKey][$i][$conn->getDomainID()] = $conn;
$this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" );
* Returns a Database object whether or not the connection was successful.
*
* @param array $server
- * @param DatabaseDomain $domainOverride Use an unspecified domain to not select any database
+ * @param DatabaseDomain $domain Domain the connection is for, possibly unspecified
* @return Database
* @throws DBAccessError
* @throws InvalidArgumentException
*/
- protected function reallyOpenConnection( array $server, DatabaseDomain $domainOverride ) {
+ protected function reallyOpenConnection( array $server, DatabaseDomain $domain ) {
if ( $this->disabled ) {
throw new DBAccessError();
}
- // Handle $domainOverride being a specified or an unspecified domain
- if ( $domainOverride->getDatabase() === null ) {
- // Normally, an RDBMS requires a DB name specified on connection and the $server
- // configuration array is assumed to already specify an appropriate DB name.
+ if ( $domain->getDatabase() === null ) {
+ // The database domain does not specify a DB name and some database systems require a
+ // valid DB specified on connection. The $server configuration array contains a default
+ // DB name to use for connections in such cases.
if ( $server['type'] === 'mysql' ) {
// For MySQL, DATABASE and SCHEMA are synonyms, connections need not specify a DB,
// and the DB name in $server might not exist due to legacy reasons (the default
$server['dbname'] = null;
}
} else {
- $server['dbname'] = $domainOverride->getDatabase();
- $server['schema'] = $domainOverride->getSchema();
+ $server['dbname'] = $domain->getDatabase();
+ }
+
+ if ( $domain->getSchema() !== null ) {
+ $server['schema'] = $domain->getSchema();
}
+ // It is always possible to connect with any prefix, even the empty string
+ $server['tablePrefix'] = $domain->getTablePrefix();
+
// Let the handle know what the cluster master is (e.g. "db1052")
$masterName = $this->getServerName( $this->getWriterIndex() );
$server['clusterMasterHost'] = $masterName;
],
'trxProfiler' => $params['trxProfiler'] ?? null,
'srvCache' => $params['srvCache'] ?? null,
- 'wanCache' => $params['wanCache'] ?? null
+ 'wanCache' => $params['wanCache'] ?? null,
+ 'localDomain' => $params['localDomain'] ?? $this->db->getDomainID()
] );
if ( isset( $params['readOnlyReason'] ) ) {
* @since 1.28
*/
public static function newFromConnection( IDatabase $db, array $params = [] ) {
- return new static( [ 'connection' => $db ] + $params );
+ return new static( array_merge(
+ [ 'localDomain' => $db->getDomainID() ],
+ $params,
+ [ 'connection' => $db ]
+ ) );
}
protected function reallyOpenConnection( array $server, DatabaseDomain $domainOverride ) {
* moved to separate EditPage and HTMLFileCache classes.
*/
class Article implements Page {
- /** @var IContextSource The context this Article is executed in */
+ /**
+ * @var IContextSource|null The context this Article is executed in.
+ * If null, REquestContext::getMain() is used.
+ */
protected $mContext;
/** @var WikiPage The WikiPage object of this instance */
protected $mPage;
- /** @var ParserOptions ParserOptions object for $wgUser articles */
+ /**
+ * @var ParserOptions|null ParserOptions object for $wgUser articles.
+ * Initialized by getParserOptions by calling $this->mPage->makeParserOptions().
+ */
public $mParserOptions;
/**
- * @var string Text of the revision we are working on
+ * @var string|null Text of the revision we are working on
* @todo BC cruft
*/
public $mContent;
/**
- * @var Content Content of the revision we are working on
+ * @var Content|null Content of the revision we are working on.
+ * Initialized by fetchContentObject().
* @since 1.21
*/
public $mContentObject;
/** @var int|null The oldid of the article that is to be shown, 0 for the current revision */
public $mOldId;
- /** @var Title Title from which we were redirected here */
+ /** @var Title|null Title from which we were redirected here, if any. */
public $mRedirectedFrom = null;
/** @var string|bool URL to redirect to or false if none */
/** @var int Revision ID of revision we are working on */
public $mRevIdFetched = 0;
- /** @var Revision Revision we are working on */
+ /**
+ * @var Revision|null Revision we are working on. Initialized by getOldIDFromRequest()
+ * or fetchContentObject().
+ */
public $mRevision = null;
- /** @var ParserOutput */
+ /**
+ * @var ParserOutput|null|false The ParserOutput generated for viewing the page,
+ * initialized by view(). If no ParserOutput could be generated, this is set to false.
+ */
public $mParserOutput;
/**
# Note that $this->mParserOutput is the *current*/oldid version output.
$pOutput = ( $outputDone instanceof ParserOutput )
? $outputDone // object fetched by hook
- : $this->mParserOutput;
+ : $this->mParserOutput ?: null; // ParserOutput or null, avoid false
# Adjust title for main page & pages with displaytitle
if ( $pOutput ) {
* Purges pages that include this page if the text was changed here.
* Every 100th edit, prune the recent changes table.
*
- * @deprecated since 1.32, use PageUpdater::doEditUpdates instead.
+ * @deprecated since 1.32, use PageUpdater::doUpdates instead.
*
* @param Revision $revision
* @param User $user User object that did the revision
// Image redirects
RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
+
+ // Purge cross-wiki cache entities referencing this page
+ self::purgeInterwikiCheckKey( $title );
}
/**
// Clear file cache for this page only
HTMLFileCache::clearFileCache( $title );
+ // Purge ?action=info cache
$revid = $revision ? $revision->getId() : null;
DeferredUpdates::addCallableUpdate( function () use ( $title, $revid ) {
InfoAction::invalidateCache( $title, $revid );
} );
+
+ // Purge cross-wiki cache entities referencing this page
+ self::purgeInterwikiCheckKey( $title );
}
/**#@-*/
+ /**
+ * Purge the check key for cross-wiki cache entries referencing this page
+ *
+ * @param Title $title
+ */
+ private static function purgeInterwikiCheckKey( Title $title ) {
+ global $wgEnableScaryTranscluding;
+
+ if ( !$wgEnableScaryTranscluding ) {
+ return; // @todo: perhaps this wiki is only used as a *source* for content?
+ }
+
+ DeferredUpdates::addCallableUpdate( function () use ( $title ) {
+ $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+ $cache->resetCheckKey(
+ // Do not include the namespace since there can be multiple aliases to it
+ // due to different namespace text definitions on different wikis. This only
+ // means that some cache invalidations happen that are not strictly needed.
+ $cache->makeGlobalKey( 'interwiki-page', wfWikiID(), $title->getDBkey() )
+ );
+ } );
+ }
+
/**
* Returns a list of categories this page is a member of.
* Results will include hidden categories
}
if ( in_array( $type, [ 'asc', 'desc' ] ) ) {
- $attrs['title'] = wfMessage( $type == 'asc' ? 'sort-ascending' : 'sort-descending' )->text();
+ $attrs['title'] = $this->msg( $type == 'asc' ? 'sort-ascending' : 'sort-descending' )->text();
}
if ( $type ) {
$args = array_slice( func_get_args(), 2 );
$message = wfMessage( $part1, $args )
->inLanguage( $parser->getOptions()->getUserLangObj() );
- if ( !$message->exists() ) {
- // When message does not exists, the message name is surrounded by angle
- // and can result in a tag, therefore escape the angles
- return $message->escaped();
- }
return [ $message->plain(), 'noparse' => false ];
} else {
return [ 'found' => false ];
* Transclude an interwiki link.
*
* @param Title $title
- * @param string $action
+ * @param string $action Usually one of (raw, render)
*
* @return string
*/
public function interwikiTransclude( $title, $action ) {
- global $wgEnableScaryTranscluding;
+ global $wgEnableScaryTranscluding, $wgTranscludeCacheExpiry;
if ( !$wgEnableScaryTranscluding ) {
return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
}
$url = $title->getFullURL( [ 'action' => $action ] );
-
- if ( strlen( $url ) > 255 ) {
+ if ( strlen( $url ) > 1024 ) {
return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
}
- return $this->fetchScaryTemplateMaybeFromCache( $url );
- }
- /**
- * @param string $url
- * @return mixed|string
- */
- public function fetchScaryTemplateMaybeFromCache( $url ) {
- global $wgTranscludeCacheExpiry;
- $dbr = wfGetDB( DB_REPLICA );
- $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
- $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
- [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
- if ( $obj ) {
- return $obj->tc_contents;
- }
-
- $req = MWHttpRequest::factory( $url, [], __METHOD__ );
- $status = $req->execute(); // Status object
- if ( $status->isOK() ) {
- $text = $req->getContent();
- } elseif ( $req->getStatus() != 200 ) {
+ $wikiId = $title->getTransWikiID(); // remote wiki ID or false
+
+ $fname = __METHOD__;
+ $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+
+ $data = $cache->getWithSetCallback(
+ $cache->makeGlobalKey(
+ 'interwiki-transclude',
+ ( $wikiId !== false ) ? $wikiId : 'external',
+ sha1( $url )
+ ),
+ $wgTranscludeCacheExpiry,
+ function ( $oldValue, &$ttl ) use ( $url, $fname, $cache ) {
+ $req = MWHttpRequest::factory( $url, [], $fname );
+
+ $status = $req->execute(); // Status object
+ if ( !$status->isOK() ) {
+ $ttl = $cache::TTL_UNCACHEABLE;
+ } elseif ( $req->getResponseHeader( 'X-Database-Lagged' ) !== null ) {
+ $ttl = min( $cache::TTL_LAGGED, $ttl );
+ }
+
+ return [
+ 'text' => $status->isOK() ? $req->getContent() : null,
+ 'code' => $req->getStatus()
+ ];
+ },
+ [
+ 'checkKeys' => ( $wikiId !== false )
+ ? [ $cache->makeGlobalKey( 'interwiki-page', $wikiId, $title->getDBkey() ) ]
+ : [],
+ 'pcGroup' => 'interwiki-transclude:5',
+ 'pcTTL' => $cache::TTL_PROC_LONG
+ ]
+ );
+
+ if ( is_string( $data['text'] ) ) {
+ $text = $data['text'];
+ } elseif ( $data['code'] != 200 ) {
// Though we failed to fetch the content, this status is useless.
- return wfMessage( 'scarytranscludefailed-httpstatus' )
- ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
+ $text = wfMessage( 'scarytranscludefailed-httpstatus' )
+ ->params( $url, $data['code'] )->inContentLanguage()->text();
} else {
- return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
+ $text = wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
}
- $dbw = wfGetDB( DB_MASTER );
- $dbw->replace( 'transcache', [ 'tc_url' ], [
- 'tc_url' => $url,
- 'tc_time' => $dbw->timestamp( time() ),
- 'tc_contents' => $text
- ] );
return $text;
}
'MediaHandlers',
'PasswordPolicy',
'RateLimits',
+ 'RawHtmlMessages',
'RecentChangesFlags',
'RemoveCredentialsBlacklist',
'RemoveGroups',
'missing' => $dependencyName,
];
}
+ if ( $constraint === '*' ) {
+ // short-circuit since any version is OK.
+ return false;
+ }
// Check if the dependency has specified a version
if ( !isset( $this->loaded[$dependencyName]['version'] ) ) {
- // If we depend upon any version, and none is set, that's fine.
- if ( $constraint === '*' ) {
- wfDebug( "{$dependencyName} does not expose its version, but {$checkedExt}"
- . " mentions it with constraint '*'. Assume it's ok so." );
- return false;
- } else {
- // Otherwise, mark it as incompatible.
- $msg = "{$dependencyName} does not expose its version, but {$checkedExt}"
- . " requires: {$constraint}.";
- return [
- 'msg' => $msg,
- 'type' => "incompatible-$type",
- 'incompatible' => $checkedExt,
- ];
- }
+ $msg = "{$dependencyName} does not expose its version, but {$checkedExt}"
+ . " requires: {$constraint}.";
+ return [
+ 'msg' => $msg,
+ 'type' => "incompatible-$type",
+ 'incompatible' => $checkedExt,
+ ];
} else {
// Try to get a constraint for the dependency version
try {
$module = $this->getModule( $row->md_module );
if ( $module ) {
$module->setFileDependencies( $context, ResourceLoaderModule::expandRelativePaths(
- FormatJson::decode( $row->md_deps, true )
+ json_decode( $row->md_deps, true )
) );
$modulesWithDeps[] = $row->md_module;
}
$out = $this->ensureNewline( $out ) . $stateScript;
}
} else {
- if ( count( $states ) ) {
- $this->errors[] = 'Problematic modules: ' .
- FormatJson::encode( $states, self::inDebugMode() );
+ if ( $states ) {
+ // Keep default escaping of slashes (e.g. "</script>") for ResourceLoaderClientHtml.
+ $this->errors[] = 'Problematic modules: ' . json_encode( $states, JSON_PRETTY_PRINT );
}
}
if ( !is_null( $deps ) ) {
$this->fileDeps[$vary] = self::expandRelativePaths(
- (array)FormatJson::decode( $deps, true )
+ (array)json_decode( $deps, true )
);
} else {
$this->fileDeps[$vary] = [];
return; // T124649; avoid write slams
}
- $deps = FormatJson::encode( $localPaths );
+ // No needless escaping as this isn't HTML output.
+ // Only stored in the database and parsed in PHP.
+ $deps = json_encode( $localPaths, JSON_UNESCAPED_SLASHES );
$dbw = wfGetDB( DB_MASTER );
$dbw->upsert( 'module_deps',
[
return $styles;
}
+ /**
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ public function getPreloadLinks( ResourceLoaderContext $context ) {
+ return $this->getLogoPreloadlinks();
+ }
+
+ /**
+ * Helper method for getPreloadLinks()
+ * @return array
+ */
+ private function getLogoPreloadlinks() {
+ $logo = $this->getLogoData( $this->getConfig() );
+
+ $tags = [];
+ $logosPerDppx = [];
+ $logos = [];
+
+ $preloadLinks = [];
+
+ if ( !is_array( $logo ) ) {
+ // No media queries required if we only have one variant
+ $preloadLinks[ $logo ] = [ 'as' => 'image' ];
+ return $preloadLinks;
+ }
+
+ if ( isset( $logo['svg'] ) ) {
+ // No media queries required if we only have a 1x and svg variant
+ // because all preload-capable browsers support SVGs
+ $preloadLinks [ $logo['svg'] ] = [ 'as' => 'image' ];
+ return $preloadLinks;
+ }
+
+ foreach ( $logo as $dppx => $src ) {
+ // Keys are in this format: "1.5x"
+ $dppx = substr( $dppx, 0, -1 );
+ $logosPerDppx[$dppx] = $src;
+ }
+
+ // Because PHP can't have floats as array keys
+ uksort( $logosPerDppx, function ( $a , $b ) {
+ $a = floatval( $a );
+ $b = floatval( $b );
+ // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
+ return $a <=> $b;
+ } );
+
+ foreach ( $logosPerDppx as $dppx => $src ) {
+ $logos[] = [ 'dppx' => $dppx, 'src' => $src ];
+ }
+
+ $logosCount = count( $logos );
+ // Logic must match ResourceLoaderSkinModule:
+ // - 1x applies to resolution < 1.5dppx
+ // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
+ // - 2x applies to resolution >= 2dppx
+ // Note that min-resolution and max-resolution are both inclusive.
+ for ( $i = 0; $i < $logosCount; $i++ ) {
+ if ( $i === 0 ) {
+ // Smallest dppx
+ // min-resolution is ">=" (larger than or equal to)
+ // "not min-resolution" is essentially "<"
+ $media_query = 'not all and (min-resolution: ' . $logos[ 1 ]['dppx'] . 'dppx)';
+ } elseif ( $i !== $logosCount - 1 ) {
+ // In between
+ // Media query expressions can only apply "not" to the entire expression
+ // (e.g. can't express ">= 1.5 and not >= 2).
+ // Workaround: Use <= 1.9999 in place of < 2.
+ $upper_bound = floatval( $logos[ $i + 1 ]['dppx'] ) - 0.000001;
+ $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] .
+ 'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
+ } else {
+ // Largest dppx
+ $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] . 'dppx)';
+ }
+
+ $preloadLinks[ $logos[$i]['src'] ] = [ 'as' => 'image', 'media' => $media_query ];
+ }
+
+ return $preloadLinks;
+ }
+
/**
* Ensure all media keys use array values.
*
}
/**
- * Non-static proxy to ::getLogo (for overloading in sub classes or tests).
- *
- * @codeCoverageIgnore
* @since 1.31
- * @param Config $conf
- * @return string|array
- */
- protected function getLogoData( Config $conf ) {
- return static::getLogo( $conf );
- }
-
- /**
* @param Config $conf
* @return string|array Single url if no variants are defined,
* or an array of logo urls keyed by dppx in form "<float>x".
* Key "1x" is always defined. Key "svg" may also be defined,
* in which case variants other than "1x" are omitted.
*/
- public static function getLogo( Config $conf ) {
+ protected function getLogoData( Config $conf ) {
$logo = $conf->get( 'Logo' );
$logoHD = $conf->get( 'LogoHD' );
if ( $context->getDebug() ) {
$mwLoaderCode .= file_get_contents( "$IP/resources/src/startup/mediawiki.log.js" );
}
+ if ( $this->getConfig()->get( 'ResourceLoaderEnableJSProfiler' ) ) {
+ $mwLoaderCode .= file_get_contents( "$IP/resources/src/startup/profiler.js" );
+ }
- $mapToJson = function ( $value ) {
- $value = FormatJson::encode( $value, ResourceLoader::inDebugMode(), FormatJson::ALL_OK );
- // Fix indentation
- $value = str_replace( "\n", "\n\t", $value );
- return $value;
- };
+ // Keep output as small as possible by disabling needless escapes that PHP uses by default.
+ // This is not HTML output, only used in a JS response.
+ $jsonFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
+ if ( ResourceLoader::inDebugMode() ) {
+ $jsonFlags |= JSON_PRETTY_PRINT;
+ }
// Perform replacements for mediawiki.js
- $mwLoaderCode = strtr( $mwLoaderCode, [
- '$VARS.baseModules' => $mapToJson( $this->getBaseModules() ),
- ] );
-
- // Perform replacements for startup.js
- $pairs = array_map( $mapToJson, [
- '$VARS.wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
- '$VARS.configuration' => $this->getConfigSettings( $context ),
- ] );
- // Raw JavaScript code (not for JSON)
- $pairs['$CODE.registrations();'] = str_replace(
- "\n",
- "\n\t",
- trim( $this->getModuleRegistrations( $context ) )
- );
- $pairs['$CODE.defineLoader();'] = $mwLoaderCode;
+ $mwLoaderPairs = [
+ '$VARS.baseModules' => json_encode( $this->getBaseModules(), $jsonFlags ),
+ ];
+ $profilerStubs = [
+ '$CODE.profileExecuteStart();' => 'mw.loader.profiler.onExecuteStart( module );',
+ '$CODE.profileExecuteEnd();' => 'mw.loader.profiler.onExecuteEnd( module );',
+ '$CODE.profileScriptStart();' => 'mw.loader.profiler.onScriptStart( module );',
+ '$CODE.profileScriptEnd();' => 'mw.loader.profiler.onScriptEnd( module );',
+ ];
+ if ( $this->getConfig()->get( 'ResourceLoaderEnableJSProfiler' ) ) {
+ // When profiling is enabled, insert the calls.
+ $mwLoaderPairs += $profilerStubs;
+ } else {
+ // When disabled (by default), insert nothing.
+ $mwLoaderPairs += array_fill_keys( array_keys( $profilerStubs ), '' );
+ }
+ $mwLoaderCode = strtr( $mwLoaderCode, $mwLoaderPairs );
+
+ // Perform string replacements for startup.js
+ $pairs = [
+ '$VARS.wgLegacyJavaScriptGlobals' => json_encode(
+ $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
+ $jsonFlags
+ ),
+ '$VARS.configuration' => json_encode(
+ $this->getConfigSettings( $context ),
+ $jsonFlags
+ ),
+ // Raw JavaScript code (not JSON)
+ '$CODE.registrations();' => trim( $this->getModuleRegistrations( $context ) ),
+ '$CODE.defineLoader();' => $mwLoaderCode,
+ ];
$startupCode = strtr( $startupCode, $pairs );
return $startupCode;
public function getDefinitionSummary( ResourceLoaderContext $context ) {
global $IP;
$summary = parent::getDefinitionSummary( $context );
- $summary[] = [
- // Detect changes to variables exposed in mw.config (T30899).
+ $startup = [
+ // getScript() exposes these variables to mw.config (T30899).
'vars' => $this->getConfigSettings( $context ),
- // Changes how getScript() creates mw.Map for mw.config
+ // getScript() uses this to decide how configure mw.Map for mw.config.
'wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
- // Detect changes to the module registrations
+ // Detect changes to the module registrations output by getScript().
'moduleHashes' => $this->getAllModuleHashes( $context ),
+ // Detect changes to base modules listed by getScript().
+ 'baseModules' => $this->getBaseModules(),
'fileHashes' => [
$this->safeFileHash( "$IP/resources/src/startup/startup.js" ),
$this->safeFileHash( "$IP/resources/src/startup/mediawiki.requestIdleCallback.js" ),
],
];
+ if ( $context->getDebug() ) {
+ $startup['fileHashes'][] = $this->safeFileHash( "$IP/resources/src/startup/mediawiki.log.js" );
+ }
+ if ( $this->getConfig()->get( 'ResourceLoaderEnableJSProfiler' ) ) {
+ $startup['fileHashes'][] = $this->safeFileHash( "$IP/resources/src/startup/profiling.js" );
+ }
+ $summary[] = $startup;
return $summary;
}
* Item class for a archive table row
*/
class RevDelArchiveItem extends RevDelRevisionItem {
- public function __construct( $list, $row ) {
- RevDelItem::__construct( $list, $row );
- $this->revision = Revision::newFromArchiveRow( $row,
- [ 'page' => $this->list->title->getArticleID() ] );
+ protected static function initRevision( $list, $row ) {
+ return Revision::newFromArchiveRow( $row,
+ [ 'page' => $list->title->getArticleID() ] );
}
public function getIdField() {
protected $lockFile;
public function __construct( $list, $row ) {
- RevDelItem::__construct( $list, $row );
- $this->file = ArchivedFile::newFromRow( $row );
+ parent::__construct( $list, $row );
$this->lockFile = RepoGroup::singleton()->getLocalRepo()->newFile( $row->fa_name );
}
+ protected static function initFile( $list, $row ) {
+ return ArchivedFile::newFromRow( $row );
+ }
+
public function getIdField() {
return 'fa_id';
}
* used via RevDelRevisionList.
*/
class RevDelArchivedRevisionItem extends RevDelArchiveItem {
- public function __construct( $list, $row ) {
- RevDelItem::__construct( $list, $row );
-
- $this->revision = Revision::newFromArchiveRow( $row,
- [ 'page' => $this->list->title->getArticleID() ] );
- }
-
public function getIdField() {
return 'ar_rev_id';
}
public function __construct( $list, $row ) {
parent::__construct( $list, $row );
- $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
+ $this->file = static::initFile( $list, $row );
+ }
+
+ /**
+ * Create file object from $row sourced from $list
+ *
+ * @param RevDelFileList $list
+ * @param mixed $row
+ * @return mixed
+ */
+ protected static function initFile( $list, $row ) {
+ return RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
}
public function getIdField() {
public function __construct( $list, $row ) {
parent::__construct( $list, $row );
- $this->revision = new Revision( $row );
+ $this->revision = static::initRevision( $list, $row );
+ }
+
+ /**
+ * Create revision object from $row sourced from $list
+ *
+ * @param RevisionListBase $list
+ * @param mixed $row
+ * @return Revision
+ */
+ protected static function initRevision( $list, $row ) {
+ return new Revision( $row );
}
public function getIdField() {
/**
* Whether the logo should be preloaded with an HTTP link header or not
+ *
+ * @deprecated since 1.32 Redundant. It now happens automatically based on whether
+ * the skin loads a stylesheet based on ResourceLoaderSkinModule, which all
+ * skins that use wgLogo in CSS do, and other's would not.
* @since 1.29
* @return bool
*/
$t = $embed . implode( "{$pop}{$embed}", $allCats['normal'] ) . $pop;
$msg = $this->msg( 'pagecategories' )->numParams( count( $allCats['normal'] ) )->escaped();
- $linkPage = wfMessage( 'pagecategorieslink' )->inContentLanguage()->text();
+ $linkPage = $this->msg( 'pagecategorieslink' )->inContentLanguage()->text();
$title = Title::newFromText( $linkPage );
$link = $title ? Linker::link( $title, $msg ) : $msg;
$s .= '<div id="mw-normal-catlinks" class="mw-normal-catlinks">' .
* @param string $message
*/
public function addToSidebar( &$bar, $message ) {
- $this->addToSidebarPlain( $bar, wfMessage( $message )->inContentLanguage()->plain() );
+ $this->addToSidebarPlain( $bar, $this->msg( $message )->inContentLanguage()->plain() );
}
/**
$attribs = [];
if ( !is_null( $tooltip ) ) {
- $attribs['title'] = wfMessage( 'editsectionhint' )->rawParams( $tooltip )
+ $attribs['title'] = $this->msg( 'editsectionhint' )->rawParams( $tooltip )
->inLanguage( $lang )->text();
}
$links = [
'editsection' => [
- 'text' => wfMessage( 'editsection' )->inLanguage( $lang )->escaped(),
+ 'text' => $this->msg( 'editsection' )->inLanguage( $lang )->escaped(),
'targetTitle' => $nt,
'attribs' => $attribs,
'query' => [ 'action' => 'edit', 'section' => $section ],
$result .= implode(
'<span class="mw-editsection-divider">'
- . wfMessage( 'pipe-separator' )->inLanguage( $lang )->escaped()
+ . $this->msg( 'pipe-separator' )->inLanguage( $lang )->escaped()
. '</span>',
$linksHtml
);
}
} else {
// article doesn't exist or is deleted
- if ( $user->isAllowed( 'deletedhistory' ) ) {
+ if ( $title->quickUserCan( 'deletedhistory', $user ) ) {
$n = $title->isDeleted();
if ( $n ) {
$undelTitle = SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedDBkey() );
// If the user can't undelete but can view deleted
// history show them a "View .. deleted" tab instead.
- $msgKey = $user->isAllowed( 'undelete' ) ? 'undelete' : 'viewdeleted';
+ $msgKey = $title->quickUserCan( 'undelete', $user ) ? 'undelete' : 'viewdeleted';
$content_navigation['actions']['undelete'] = [
'class' => $this->getTitle()->isSpecial( 'Undelete' ) ? 'selected' : false,
'text' => wfMessageFallback( "$skname-action-$msgKey", "{$msgKey}_short" )
$form->addPreText(
Html::openElement( 'dl' )
- . Html::element( 'dt', [], wfMessage( 'credentialsform-provider' )->text() )
+ . Html::element( 'dt', [], $this->msg( 'credentialsform-provider' )->text() )
. Html::element( 'dd', [], $info['provider'] )
- . Html::element( 'dt', [], wfMessage( 'credentialsform-account' )->text() )
+ . Html::element( 'dt', [], $this->msg( 'credentialsform-account' )->text() )
. Html::element( 'dd', [], $info['account'] )
. Html::closeElement( 'dl' )
);
$dateRangeSelection = Html::rawElement(
'div',
[],
- Xml::label( wfMessage( 'date-range-from' )->text(), 'mw-date-start' ) . ' ' .
+ Xml::label( $this->msg( 'date-range-from' )->text(), 'mw-date-start' ) . ' ' .
new DateInputWidget( [
'infusable' => true,
'id' => 'mw-date-start',
'value' => $this->opts['start'],
'longDisplayFormat' => true,
] ) . '<br>' .
- Xml::label( wfMessage( 'date-range-to' )->text(), 'mw-date-end' ) . ' ' .
+ Xml::label( $this->msg( 'date-range-to' )->text(), 'mw-date-end' ) . ' ' .
new DateInputWidget( [
'infusable' => true,
'id' => 'mw-date-end',
$opts->validateIntBounds( 'limit', 0, $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
if ( $par !== null ) {
- $opts->setValue( 'target', $par );
+ // Beautify the username
+ $par = User::getCanonicalName( $par, false );
+ $opts->setValue( 'target', (string)$par );
}
$ns = $opts->getValue( 'namespace' );
* SPF and bounce problems with some mailers (see below).
*/
$mailFrom = new MailAddress( $config->get( 'PasswordSender' ),
- wfMessage( 'emailsender' )->inContentLanguage()->text() );
+ $context->msg( 'emailsender' )->inContentLanguage()->text() );
$replyTo = $from;
} else {
/**
if ( $config->get( 'UserEmailUseReplyTo' ) ) {
$mailFrom = new MailAddress(
$config->get( 'PasswordSender' ),
- wfMessage( 'emailsender' )->inContentLanguage()->text()
+ $context->msg( 'emailsender' )->inContentLanguage()->text()
);
$replyTo = $ccFrom;
} else {
$pages = [];
foreach ( $res as $row ) {
- $pages[] = Title::makeName( $row->page_title, $row->page_namespace );
+ $pages[] = Title::makeName( $row->page_namespace, $row->page_title );
}
return $pages;
$pages = [];
foreach ( $res as $row ) {
- $pages[] = Title::makeName( $row->page_title, $row->page_namespace );
+ $pages[] = Title::makeName( $row->page_namespace, $row->page_title );
}
return $pages;
if ( !$this->isActionAllowed( $this->authAction ) ) {
if ( $this->authAction === AuthManager::ACTION_LINK ) {
// looks like no linking provider is installed or willing to take this user
- $titleMessage = wfMessage( 'cannotlink-no-provider-title' );
- $errorMessage = wfMessage( 'cannotlink-no-provider' );
+ $titleMessage = $this->msg( 'cannotlink-no-provider-title' );
+ $errorMessage = $this->msg( 'cannotlink-no-provider' );
throw new ErrorPageError( $titleMessage, $errorMessage );
} else {
// user probably back-button-navigated into an auth session that no longer exists
}
$status = StatusValue::newGood();
- $status->warning( wfMessage( 'unlinkaccounts-success' ) );
+ $status->warning( $this->msg( 'unlinkaccounts-success' ) );
$this->loadAuth( $subPage, null, true ); // update requests so the unlinked one doesn't show up
// Reset sessions - if the user unlinked an account because it was compromised,
$this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file
$commentDefault = '';
- $commentMsg = wfMessage( 'upload-default-description' )->inContentLanguage();
+ $commentMsg = $this->msg( 'upload-default-description' )->inContentLanguage();
if ( !$this->mForReUpload && !$commentMsg->isDisabled() ) {
$commentDefault = $commentMsg->plain();
}
} elseif ( $warning == 'no-change' ) {
$file = $args;
$filename = $file->getTitle()->getPrefixedText();
- $msg = "\t<li>" . wfMessage( 'fileexists-no-change', $filename )->parse() . "</li>\n";
+ $msg = "\t<li>" . $this->msg( 'fileexists-no-change', $filename )->parse() . "</li>\n";
} elseif ( $warning == 'duplicate-version' ) {
$file = $args[0];
$count = count( $args );
$filename = $file->getTitle()->getPrefixedText();
- $message = wfMessage( 'fileexists-duplicate-version' )
+ $message = $this->msg( 'fileexists-duplicate-version' )
->params( $filename )
->numParams( $count );
$msg = "\t<li>" . $message->parse() . "</li>\n";
$ltitle = SpecialPage::getTitleFor( 'Log' );
$llink = $linkRenderer->makeKnownLink(
$ltitle,
- wfMessage( 'deletionlog' )->text(),
+ $this->msg( 'deletionlog' )->text(),
[],
[
'type' => 'delete',
'page' => Title::makeTitle( NS_FILE, $args )->getPrefixedText(),
]
);
- $msg = "\t<li>" . wfMessage( 'filewasdeleted' )->rawParams( $llink )->parse() . "</li>\n";
+ $msg = "\t<li>" . $this->msg( 'filewasdeleted' )->rawParams( $llink )->parse() . "</li>\n";
} elseif ( $warning == 'duplicate' ) {
$msg = $this->getDupeWarning( $args );
} elseif ( $warning == 'duplicate-archive' ) {
if ( $type !== 'file' && $type !== 'thumb' ) {
throw new UploadStashBadPathException(
- wfMessage( 'uploadstash-bad-path-unknown-type', $type )
+ $this->msg( 'uploadstash-bad-path-unknown-type', $type )
);
}
$fileName = strtok( '/' );
$srcNamePos = strrpos( $thumbPart, $fileName );
if ( $srcNamePos === false || $srcNamePos < 1 ) {
throw new UploadStashBadPathException(
- wfMessage( 'uploadstash-bad-path-unrecognized-thumb-name' )
+ $this->msg( 'uploadstash-bad-path-unrecognized-thumb-name' )
);
}
$paramString = substr( $thumbPart, 0, $srcNamePos - 1 );
return [ 'file' => $file, 'type' => $type, 'params' => $params ];
} else {
throw new UploadStashBadPathException(
- wfMessage( 'uploadstash-bad-path-no-handler', $file->getMimeType(), $file->getPath() )
+ $this->msg( 'uploadstash-bad-path-no-handler', $file->getMimeType(), $file->getPath() )
);
}
}
$thumbnailImage = $file->transform( $params, $flags );
if ( !$thumbnailImage ) {
throw new UploadStashFileNotFoundException(
- wfMessage( 'uploadstash-file-not-found-no-thumb' )
+ $this->msg( 'uploadstash-file-not-found-no-thumb' )
);
}
// we should have just generated it locally
if ( !$thumbnailImage->getStoragePath() ) {
throw new UploadStashFileNotFoundException(
- wfMessage( 'uploadstash-file-not-found-no-local-path' )
+ $this->msg( 'uploadstash-file-not-found-no-local-path' )
);
}
$this->stash->repo, $thumbnailImage->getStoragePath(), false );
if ( !$thumbFile ) {
throw new UploadStashFileNotFoundException(
- wfMessage( 'uploadstash-file-not-found-no-object' )
+ $this->msg( 'uploadstash-file-not-found-no-object' )
);
}
if ( !$status->isOK() ) {
$errors = $status->getErrorsArray();
throw new UploadStashFileNotFoundException(
- wfMessage(
+ $this->msg(
'uploadstash-file-not-found-no-remote-thumb',
print_r( $errors, 1 ),
$scalerThumbUrl
$contentType = $req->getResponseHeader( "content-type" );
if ( !$contentType ) {
throw new UploadStashFileNotFoundException(
- wfMessage( 'uploadstash-file-not-found-missing-content-type' )
+ $this->msg( 'uploadstash-file-not-found-missing-content-type' )
);
}
private function outputLocalFile( File $file ) {
if ( $file->getSize() > self::MAX_SERVE_BYTES ) {
throw new SpecialUploadStashTooLargeException(
- wfMessage( 'uploadstash-file-too-large', self::MAX_SERVE_BYTES )
+ $this->msg( 'uploadstash-file-too-large', self::MAX_SERVE_BYTES )
);
}
$size = strlen( $content );
if ( $size > self::MAX_SERVE_BYTES ) {
throw new SpecialUploadStashTooLargeException(
- wfMessage( 'uploadstash-file-too-large', self::MAX_SERVE_BYTES )
+ $this->msg( 'uploadstash-file-too-large', self::MAX_SERVE_BYTES )
);
}
// Cancel output buffering and gzipping if set
if ( $thumb ) {
return $thumb->toHtml( [ 'desc-link' => true ] );
} else {
- return wfMessage( 'thumbnail_error', '' )->escaped();
+ return $this->msg( 'thumbnail_error', '' )->escaped();
}
} else {
return htmlspecialchars( $value );
Hooks::run( 'SpecialListusersHeaderForm', [ $this, &$beforeSubmitButtonHookOut ] );
if ( $beforeSubmitButtonHookOut !== '' ) {
- $formDescriptior[ 'beforeSubmitButtonHookOut' ] = [
+ $formDescriptor[ 'beforeSubmitButtonHookOut' ] = [
'class' => HTMLInfoField::class,
'raw' => true,
'default' => $beforeSubmitButtonHookOut
Hooks::run( 'SpecialListusersHeader', [ $this, &$beforeClosingFieldsetHookOut ] );
if ( $beforeClosingFieldsetHookOut !== '' ) {
- $formDescriptior[ 'beforeClosingFieldsetHookOut' ] = [
+ $formDescriptor[ 'beforeClosingFieldsetHookOut' ] = [
'class' => HTMLInfoField::class,
'raw' => true,
'default' => $beforeClosingFieldsetHookOut
--- /dev/null
+<?php
+
+namespace MediaWiki\Widget;
+
+/**
+ * Check matrix widget. Displays a matrix of checkboxes for given options
+ *
+ * @copyright 2018 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license MIT
+ */
+class CheckMatrixWidget extends \OOUI\Widget {
+
+ protected $name = '';
+ protected $columns = [];
+ protected $rows = [];
+ protected $tooltips = [];
+ protected $values = [];
+ protected $forcedOn = [];
+ protected $forcedOff = [];
+
+ /**
+ * CheckMatrixWidget constructor
+ *
+ * Operates similarly to MultiSelectWidget, but instead of using an array of
+ * options, uses an array of rows and an array of columns to dynamically
+ * construct a matrix of options. The tags used to identify a particular cell
+ * are of the form "columnName-rowName"
+ *
+ * @param array $config Configuration array with the following options:
+ * - columns
+ * - Required list of columns in the matrix.
+ * - rows
+ * - Required list of rows in the matrix.
+ * - force-options-on
+ * - Accepts array of column-row tags to be displayed as enabled but unavailable to change
+ * - force-options-off
+ * - Accepts array of column-row tags to be displayed as disabled but unavailable to change.
+ * - tooltips
+ * - Optional array mapping row label to tooltip content
+ * - tooltip-class
+ * - Optional CSS class used on tooltip container span. Defaults to mw-icon-question.
+ */
+ public function __construct( array $config = [] ) {
+ // Configuration initialization
+
+ parent::__construct( $config );
+
+ $this->name = isset( $config['name'] ) ?
+ $config[ 'name' ] : null;
+ $this->id = isset( $config['id'] ) ?
+ $config['id'] : null;
+
+ // Properties
+ $this->rows = isset( $config['rows'] ) ?
+ $config['rows'] : [];
+ $this->columns = isset( $config['columns'] ) ?
+ $config['columns'] : [];
+ $this->tooltips = isset( $config['tooltips'] ) ?
+ $config['tooltips'] : [];
+
+ $this->values = isset( $config['values'] ) ?
+ $config['values'] : [];
+
+ $this->forcedOn = isset( $config['forcedOn'] ) ?
+ $config['forcedOn'] : [];
+ $this->forcedOff = isset( $config['forcedOff'] ) ?
+ $config['forcedOff'] : [];
+
+ // Build the table
+ $table = new \OOUI\Tag( 'table' );
+ $tr = new \OOUI\Tag( 'tr' );
+ // Build the header
+ $tr->appendContent( $this->getCellTag( "\u{00A0}" ) );
+ foreach ( $this->columns as $columnLabel => $columnTag ) {
+ $tr->appendContent(
+ $this->getCellTag( $columnLabel )
+ );
+ }
+ $table->appendContent( $tr );
+
+ // Build the options matrix
+ foreach ( $this->rows as $rowLabel => $rowTag ) {
+ $table->appendContent(
+ $this->getTableRow( $rowLabel, $rowTag )
+ );
+ }
+
+ // Initialization
+ $this->addClasses( [ 'mw-widget-checkMatrixWidget' ] );
+ $this->appendContent( $table );
+ }
+
+ /**
+ * Get a formatted table row for the option, with
+ * a checkbox widget.
+ *
+ * @param string $label Row label
+ * @param string $tag Row tag name
+ * @return \OOUI\Tag The resulting table row
+ */
+ private function getTableRow( $label, $tag ) {
+ $row = new \OOUI\Tag( 'tr' );
+ $tooltip = $this->getTooltip( $label );
+ $labelFieldConfig = $tooltip ? [ 'help' => $tooltip ] : [];
+ // Build label cell
+ $labelField = new \OOUI\FieldLayout(
+ new \OOUI\Widget(), // Empty widget, since we don't have the checkboxes here
+ [
+ 'label' => $label,
+ 'align' => 'inline',
+ ] + $labelFieldConfig
+ );
+ $row->appendContent( $this->getCellTag( $labelField ) );
+
+ // Build checkbox column cells
+ foreach ( $this->columns as $columnTag ) {
+ $thisTag = "$columnTag-$tag";
+
+ // Construct a checkbox
+ $checkbox = new \OOUI\CheckboxInputWidget( [
+ 'value' => $thisTag,
+ 'name' => $this->name ? "{$this->name}[]" : null,
+ 'id' => $this->id ? "{$this->id}-$thisTag" : null,
+ 'selected' => $this->isTagChecked( $thisTag ),
+ 'disabled' => $this->isTagDisabled( $thisTag ),
+ ] );
+
+ $row->appendContent( $this->getCellTag( $checkbox ) );
+ }
+ return $row;
+ }
+
+ /**
+ * Get an individual cell tag with requested content
+ *
+ * @param string $content Content for the <td> cell
+ * @return \OOUI\Tag Resulting cell
+ */
+ private function getCellTag( $content ) {
+ $cell = new \OOUI\Tag( 'td' );
+ $cell->appendContent( $content );
+ return $cell;
+ }
+
+ /**
+ * Check whether the given tag's checkbox should
+ * be checked
+ *
+ * @param string $tagName Tag name
+ * @return boolean Tag should be checked
+ */
+ private function isTagChecked( $tagName ) {
+ // If the tag is in the value list
+ return in_array( $tagName, (array)$this->values, true ) ||
+ // Or if the tag is forced on
+ in_array( $tagName, (array)$this->forcedOn, true );
+ }
+
+ /**
+ * Check whether the given tag's checkbox should
+ * be disabled
+ *
+ * @param string $tagName Tag name
+ * @return boolean Tag should be disabled
+ */
+ private function isTagDisabled( $tagName ) {
+ return (
+ // If the entire widget is disabled
+ $this->isDisabled() ||
+ // If the tag is 'forced on' or 'forced off'
+ in_array( $tagName, (array)$this->forcedOn, true ) ||
+ in_array( $tagName, (array)$this->forcedOff, true )
+ );
+ }
+
+ /**
+ * Get the tooltip help associated with this row
+ *
+ * @param string $label Label name
+ * @return string Tooltip. Null if none is available.
+ */
+ private function getTooltip( $label ) {
+ return isset( $this->tooltips[ $label ] ) ?
+ $this->tooltips[ $label ] : null;
+ }
+
+ protected function getJavaScriptClassName() {
+ return 'mw.widgets.CheckMatrixWidget';
+ }
+
+ public function getConfig( &$config ) {
+ $config += [
+ 'name' => $this->name,
+ 'id' => $this->id,
+ 'rows' => $this->rows,
+ 'columns' => $this->columns,
+ 'tooltips' => $this->tooltips,
+ 'forcedOff' => $this->forcedOff,
+ 'forcedOn' => $this->forcedOn,
+ 'values' => $this->values,
+ ];
+ return parent::getConfig( $config );
+ }
+}
$warningDone = true;
}
$startPos += 2;
- continue;
+ break;
}
// Recursively parse another rule
$inner .= $this->recursiveConvertRule( $text, $variant, $startPos, $depth + 1 );
"confirm-unwatch-top": "¿Desaniciar esta páxina de la to llista de vixilancia?",
"confirm-rollback-button": "Aceutar",
"confirm-rollback-top": "¿Revertir les ediciones a esta páxina?",
+ "confirm-mcrundo-title": "Desfacer un cambéu",
+ "mcrundofailed": "Falló desfacer",
+ "mcrundo-missingparam": "Faltan parámetros riquíos na solicitú.",
+ "mcrundo-changed": "La páxina cambió desque visti les diferencies. Revisa'l cambiu nuevu.",
"quotation-marks": "«$1»",
"imgmultipageprev": "← páxina anterior",
"imgmultipagenext": "páxina siguiente →",
"upload-form-label-not-own-work-message-generic-foreign": "Калі вы ня можаце загрузіць гэты файл паводле правілаў агульнага сховішча, калі ласка, закрыйце гэты дыялёг і паспрабуйце іншы мэтад.",
"upload-form-label-not-own-work-local-generic-foreign": "Вы можаце паспрабаваць скарыстацца [[Special:Upload|старонкай загрузкі {{GRAMMAR:родны|{{SITENAME}}}}]], калі гэты файл можна туды загрузіць згодна з правіламі.",
"backend-fail-stream": "Немагчыма накіраваць файл $1.",
- "backend-fail-backup": "Немагчыма зрабіць рэзэрвовую копію файла $1.",
+ "backend-fail-backup": "Немагчыма зрабіць рэзэрвовую копію файлу «$1».",
"backend-fail-notexists": "Файл $1 не існуе.",
"backend-fail-hashes": "Немагчыма атрымаць хэшы файлаў для параўнаньня.",
- "backend-fail-notsame": "Ð\9dеÑ\96дÑ\8dнÑ\82Ñ\8bÑ\84Ñ\96каванÑ\8b Ñ\84айл Ñ\83жо Ñ\96Ñ\81нÑ\83е $1.",
- "backend-fail-invalidpath": "$1 не зьяўляецца слушным шляхам да сховішча.",
+ "backend-fail-notsame": "Ужо Ñ\96Ñ\81нÑ\83е неÑ\96дÑ\8dнÑ\82Ñ\8bÑ\87нÑ\8b Ñ\84айл «$1».",
+ "backend-fail-invalidpath": "«$1» не зьяўляецца слушным шляхам да сховішча.",
"backend-fail-delete": "Немагчыма выдаліць файл $1.",
"backend-fail-describe": "Не атрымалася зьмяніць мэтазьвесткі для файла «$1».",
"backend-fail-alreadyexists": "Файл $1 ужо існуе.",
"confirm-unwatch-top": "Выдаліць гэтую старонку з Вашага сьпісу назіраньня?",
"confirm-rollback-button": "Так",
"confirm-rollback-top": "Адкаціць праўкі на гэтай старонцы?",
+ "confirm-mcrundo-title": "Адмяніць зьмену",
+ "mcrundofailed": "Адмена не атрымалася",
+ "mcrundo-missingparam": "Адсутнічаюць абавязковыя парамэтры для запыту.",
+ "mcrundo-changed": "Гэтая старонка была зьмененая з моманту, калі вы праглядалі зьмены. Калі ласка, праглядзіце новую зьмену.",
"quotation-marks": "«$1»",
"imgmultipageprev": "← папярэдняя старонка",
"imgmultipagenext": "наступная старонка →",
"Andrus",
"Da voli",
"OlegCinema",
- "Gorgich"
+ "Gorgich",
+ "Vlad5250"
]
},
"tog-underline": "Падкрэсліваць спасылкі:",
"right-bot": "Лічыцца аўтаматычным працэсам",
"right-nominornewtalk": "Не паведамляць пра новыя паведамленні ў адказ на дробныя праўкі размоўных старонак",
"right-apihighlimits": "Карыстацца вышэйшымі лімітамі ў API-зваротах",
- "right-writeapi": "Карыстацца праграмным інтэрфейсам запісу (write API)",
+ "right-writeapi": "Карыстацца праграмным інтэрфейсам запісу (''write API'')",
"right-delete": "Выдаляць старонкі",
"right-bigdelete": "Выдаляць старонкі з вялікімі гісторыямі",
"right-deletelogentry": "Выдаляць і аднаўляць асобныя запісы журналаў",
"cascadeprotected": "Тази страница е защитена против редактиране, защото е включена в {{PLURAL:$1|следната страница, която от своя страна има|следните страници, които от своя страна имат}} „каскадна“ защита:\n$2",
"namespaceprotected": "Нямате права за редактиране на страници в именно пространство <strong>$1</strong>.",
"customcssprotected": "Нямате права за редактиране на тази CSS страница, защото тя съдържа чужди потребителски настройки.",
+ "customjsonprotected": "Нямате права за редактиране на тази JSON страница, защото тя съдържа чужди потребителски настройки.",
"customjsprotected": "Нямате права за редактиране на тази JavaScript страница, тъй като съдържа чужди потребителски настройки.",
"mycustomcssprotected": "Нямате права за редактиране на тази CSS страница.",
+ "mycustomjsonprotected": "Нямате права за редактиране на тази JSON страница.",
"mycustomjsprotected": "Нямате права за редактиране на тази JavaScript страница.",
"myprivateinfoprotected": "Нямате права да редактирате личната си информация.",
"mypreferencesprotected": "Нямате права да редактирате настройките си.",
"ns-specialprotected": "Специалните страници не могат да бъдат редактирани.",
"titleprotected": "Тази страница е била защитена срещу създаване от [[User:$1|$1]].\nПосочената причина е <em>$2</em>.",
"filereadonlyerror": "Файлът „$1“ не може да бъде променен, тъй като файловото хранилище „$2“ е в режим само за четене.\n\nСистемният администратор, който го е заключил, е посочил следната причина: „$3“.",
+ "invalidtitle": "Невалидно заглавие",
"invalidtitle-knownnamespace": "Невалидно заглавие с именно пространство „$2“ и текст „$3“",
"invalidtitle-unknownnamespace": "Невалидно заглавие с неразпознато именно пространство номер $1 и текст „$2“",
"exception-nologin": "Не сте влезли в системата",
"rcfilters-group-results-by-page": "Групиране на резултатите по страница",
"rcfilters-activefilters": "Активни филтри",
"rcfilters-activefilters-hide": "Скриване",
+ "rcfilters-activefilters-show": "Показване",
"rcfilters-advancedfilters": "Разширени филтри",
"rcfilters-limit-title": "Резултати за показване",
"rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|промяна|промени}}, $2",
"cascadeprotected": "Uređivanje ove stranice zabranjeno je jer se koristi u {{PLURAL:$1|sljedećoj stranici, koja je zaštićena|sljedećim stranicama, koje su zaštićene}} prenosivom zaštitom:\n$2",
"namespaceprotected": "Vi nemate dozvulu da mijenjate stranicu '''$1'''.",
"customcssprotected": "Nemate dozvolu za mijenjanje ove CSS stranice jer sadrži osobne postavke nekog drugog korisnika.",
+ "customjsonprotected": "Nemate dozvolu za mijenjanje ove JSON stranice jer sadrži lične postavke drugog korisnika.",
"customjsprotected": "Nemate dozvolu za mijenjanje ove JavaScript stranice jer sadrži osobne postavke nekog drugog korisnika.",
"mycustomcssprotected": "Nemate dozvolu da uređujete ovu CSS stranicu.",
+ "mycustomjsonprotected": "Nemate dozvolu da uređujete ovu JSON stranicu.",
"mycustomjsprotected": "Nemate dozvolu da uređujete ovu stranicu sa JavaScriptom.",
"myprivateinfoprotected": "Nemate dozvolu da uređujete svoje privatne informacije.",
"mypreferencesprotected": "Nemate dozvolu da uređujete svoje postavke.",
"Abella",
"Pierpao",
"Amire80",
- "Leptictidium"
+ "Leptictidium",
+ "Townie"
]
},
"tog-underline": "Subratlla els enllaços:",
"grouppage-bureaucrat": "{{ns:project}}:Buròcrates",
"grouppage-suppress": "{{ns:project}}:Supressors de Flow",
"right-read": "Llegir pàgines",
- "right-edit": "Modifica les pàgines",
+ "right-edit": "Modificar les pàgines",
"right-createpage": "Crear pàgines (que no són de discussió)",
"right-createtalk": "Crear pàgines de discussió",
"right-createaccount": "Crear nous comptes",
"uploadstash-bad-path-invalid": "El camí no és vàlid.",
"uploadstash-bad-path-unknown-type": "El tipus «$1» és desconegut.",
"uploadstash-bad-path-unrecognized-thumb-name": "Nom de miniatura no reconegut.",
+ "uploadstash-file-not-found-no-thumb": "No s'ha pogut obtenir una miniatura.",
"uploadstash-file-not-found-not-exists": "No es pot trobar el camí, o bé no és un fitxer pla.",
"uploadstash-file-too-large": "No es pot servir un fitxer més gran de $1 bytes.",
+ "uploadstash-not-logged-in": "Cap usuari ha iniciat una sessió. Els fitxers han de pertànyer als usuaris.",
+ "uploadstash-wrong-owner": "Aquest fitxer ($1) no pertany a l'usuari actual.",
"uploadstash-no-extension": "L’extensió és nul·la.",
"uploadstash-zero-length": "El fitxer té mida zero.",
"invalid-chunk-offset": "El desplaçament del fragment no és vàlid",
"http-timed-out": "La petició HTTP ha expirat.",
"http-curl-error": "Error en recuperar l'URL: $1",
"http-bad-status": "Hi ha hagut un problema durant la petició HTTP: $1 $2",
+ "http-internal-error": "Error intern HTTP.",
"upload-curl-error6": "No s'ha pogut accedir a l'URL",
"upload-curl-error6-text": "No s'ha pogut accedir a l'URL que s'ha proporcionat. Torneu a comprovar que sigui correcte i que el lloc estigui funcionant.",
"upload-curl-error28": "S'ha excedit el temps d'espera de la càrrega",
"filehist-filesize": "Mida del fitxer",
"filehist-comment": "Comentari",
"imagelinks": "Ús del fitxer",
- "linkstoimage": "{{PLURAL:$1|La pàgina següent enllaça|Les $1 pàgines següents enllacen}} a aquest fitxer:",
+ "linkstoimage": "{{PLURAL:$1|La pàgina següent utilitza|Les $1 pàgines següents utilitzen}} aquest fitxer:",
"linkstoimage-more": "Hi ha més de $1 {{PLURAL:$1|pàgina que enllaça|pàgines que enllacen}} a aquest fitxer.\nLa següent llista només mostra {{PLURAL:$1|la primera d'aquestes pàgines|les primeres $1 d'aquestes pàgines}}.\nPodeu consultar la [[Special:WhatLinksHere/$2|llista completa]].",
"nolinkstoimage": "No hi ha pàgines que enllacin a aquesta imatge.",
"morelinkstoimage": "Visualitza [[Special:WhatLinksHere/$1|més enllaços]] que porten al fitxer.",
"namespace_association": "Espai de noms associat",
"tooltip-namespace_association": "Marqueu aquesta casella per incloure l'espai de noms de discussió o de no discussió associat a l'espai de noms seleccionat",
"blanknamespace": "(Principal)",
- "contributions": "Contribucions de {{GENDER:$1|l’usuari|la usuària}}",
+ "contributions": "Contribucions de {{GENDER:$1|l'usuari|la usuària}}",
"contributions-title": "Contribucions de l'usuari $1",
"mycontris": "Contribucions",
"anoncontribs": "Contribucions",
"sp-contributions-newbies-title": "Contribucions dels comptes d'usuari més nous",
"sp-contributions-blocklog": "Registre de blocatges",
"sp-contributions-suppresslog": "contribucions suprimides de {{GENDER:$1|l'usuari|la usuària}}",
- "sp-contributions-deleted": "Contribucions de {{GENDER:$1|l’usuari|la usuària}} esborrades",
+ "sp-contributions-deleted": "Contribucions de {{GENDER:$1|l'usuari|la usuària}} esborrades",
"sp-contributions-uploads": "càrregues",
"sp-contributions-logs": "registres",
"sp-contributions-talk": "discussió",
"confirm-unwatch-top": "Voleu treure aquesta pàgina de la llista de seguiment?",
"confirm-rollback-button": "D'acord",
"confirm-rollback-top": "Voleu revertir les modificacions a la pàgina?",
+ "confirm-mcrundo-title": "Desfés un canvi",
"colon-separator": ": ",
"quotation-marks": "«$1»",
"imgmultipageprev": "← pàgina anterior",
"botpasswords-label-appid": "Ботан цӀе:",
"botpasswords-label-create": "Кхолла",
"botpasswords-label-update": "Карлаяккха",
- "botpasswords-label-cancel": "Юхаяккха",
+ "botpasswords-label-cancel": "Юхаяккхар",
"botpasswords-label-delete": "ДӀаяккхар",
"botpasswords-label-resetpassword": "Пароль кхоссар",
"botpasswords-label-grants": "Лелош йолу шоралаш:",
"rcfilters-savedqueries-remove": "ДӀаяккха",
"rcfilters-savedqueries-new-name-label": "ЦӀе",
"rcfilters-savedqueries-apply-label": "Ӏалашде нисъяр",
- "rcfilters-savedqueries-cancel-label": "ЦаоÑ\8cÑ\88Ñ\83",
+ "rcfilters-savedqueries-cancel-label": "ЮÑ\85аÑ\8fккÑ\85аÑ\80",
"rcfilters-savedqueries-add-new-title": "Ӏалашде литтар нисъяр",
"rcfilters-restore-default-filters": "Литтарш Ӏадйитаран кепе меттахӀоттае",
"rcfilters-clear-all-filters": "Ерриге литтарш цӀанъян",
"anonymous": "{{PLURAL:$1|1=ЦӀе хьулйина декъашхо|ЦӀе хьулйина декъашхой}} {{grammar:genitive|{{SITENAME}}}}",
"siteuser": "декъашхо {{grammar:genitive|{{SITENAME}}}} $1",
"anonuser": "цӀе хьулйина декъашхо {{grammar:genitive|{{SITENAME}}}} $1",
- "lastmodifiedatby": "Ð¥Ó\80аÑ\80а агÓ\80о Ñ\82Ó\80аÑ\8cÑ\85Ñ\85Ñ\8cаÑ\80а Ñ\85ийÑ\86ина: $1 $2, Ñ\85ийÑ\86ам бина — $3",
+ "lastmodifiedatby": "Ð¥Ó\80аÑ\80а агÓ\80о Ñ\82Ó\80аÑ\8cÑ\85Ñ\85Ñ\8cаÑ\80а Ñ\85ийÑ\86ам бина: $1 $2, Ñ\85ийÑ\86аман авÑ\82оÑ\80 — $3",
"othercontribs": "Кхуллуш дакъалецира декъашхоша: $1.",
"others": "кхин",
"siteusers": "{{PLURAL:$2|1=декъашхо|декъашхой}} {{grammar:genitive|{{SITENAME}}}} $1",
"tag-mw-changed-redirect-target": "хийцаран бахьна ду дӀасахьажорг",
"tag-mw-blank": "цӀанъяр",
"tag-mw-rollback": "Юхаяккха",
- "tag-mw-undo": "Ñ\86аоÑ\8cÑ\88Ñ\83",
+ "tag-mw-undo": "Ñ\8eÑ\85аÑ\8fккÑ\85аÑ\80",
"tags-title": "Билгалонаш",
"tags-intro": "ХӀокху агӀона чохь гойтуш бу билгалонийн могӀам царца программин латторо билгал доху нисдарш, кхин билгалонийн маьӀна а.",
"tags-tag": "Билгалона цӀе",
"feedback-adding": "АгӀона хетарг тӀетохар...",
"feedback-back": "ЮхагӀо",
"feedback-bugornote": "Хьайн техникин халонах лаьцна яздан хӀума делахь, дехар до, [$1 хаам бе тхоьга].\nДацахь хьан йиш ю хӀокху атта кепаца «[$3 $2]» агӀонг коммент тӀетоха хьан декъашхочун цӀарца, кхин лелош йолу браузер билгал еш.",
- "feedback-cancel": "ЦаоÑ\8cÑ\88Ñ\83",
+ "feedback-cancel": "ЮÑ\85аÑ\8fккÑ\85аÑ\80",
"feedback-close": "Кийчча ю",
"feedback-message": "Хаам:",
"feedback-subject": "Тема:",
"rcfilters-view-tags": "دەستکارییە تاگکراوەکان",
"rcfilters-view-tags-help-icon-tooltip": "زیاتر بزانە لەسەر دەستکارییە تاگکراوەکان",
"rcfilters-liveupdates-button": "نوێکردنەوەی زیندوو",
+ "rcfilters-watchlist-edit-watchlist-button": "دەستکاریکردنی پێڕستی پەڕە چاودێریکراوەکانت",
+ "rcfilters-watchlist-showupdated": "ئەو پەڕانەی دەستکاریکراون و لەکاتی دەستکاریکردنەکەوە سەردانت نەکردوونەتەوە بە <strong>تۆخ</strong> دەردەکەون، بە نیشانی پڕکراوەوە.",
"rcnotefrom": "ژێرەوە {{PLURAL:$5|گۆڕانکارییەکەیە|گۆڕانکارییەکانە}} لە <strong>$3، $4</strong>ەوە (ھەتا <strong>$1</strong> نیشان دراوە).",
"rclistfromreset": "گەڕاندنەوەی ھەڵبژاردەی بەروار",
"rclistfrom": "گۆڕانکارییە نوێکان نیشان بدە بە دەستپێکردن لە $3 $2",
"import-mapping-namespace": "Importovat do jmenného prostoru:",
"import-mapping-subpage": "Importovat jako podstránky následující stránky:",
"import-upload-filename": "Jméno souboru:",
+ "import-upload-username-prefix": "Interwiki prefix:",
"import-assign-known-users": "Přiřazovat editace lokálním uživatelům, pokud zde existuje uživatel s daným jménem",
"import-comment": "Zdůvodnění:",
"importtext": "Prosím exportujte soubor ze zdrojové wiki pomocí [[Special:Export|exportního nástroje]].\nUložte jej na svůj disk a nahrajte ho sem.",
"pagedata-not-acceptable": "Nenalezen odpovídající formát. Podporované MIME typy: $1",
"pagedata-bad-title": "Neplatný název: $1.",
"unregistered-user-config": "Z bezpečnostních důvodů nelze načítat uživatelské podstránky s JavaScriptem, CSS nebo JSONem u neregistrovaných uživatelů.",
+ "passwordpolicies": "Zásady pro heslo",
"passwordpolicies-group": "Skupina",
"passwordpolicies-policy-minimalpasswordlength": "Heslo musí být alespoň {{PLURAL:$1|$1 znak|$1 znaky|$1 znaků}} dlouhé",
"passwordpolicies-policy-minimumpasswordlengthtologin": "Pro přihlášení je vyžadováno alespoň {{PLURAL:$1|$1 znak|$1 znaky|$1 znaků}} dlouhé heslo",
"resetpass-submit-loggedin": "Skift adgangskode",
"resetpass-submit-cancel": "Annuller",
"resetpass-wrong-oldpass": "Ugyldig midlertidig eller gældende adgangskode.\nDu har muligvis allerede ændret din adgangskode eller bedt om en ny midlertidig kode.",
- "resetpass-recycled": "Vær venlig at ændre din adgangskode til noget andet end din nuværende adgangskode.",
+ "resetpass-recycled": "Ændr venligst din adgangskode til noget andet end din nuværende adgangskode.",
"resetpass-temp-emailed": "Du loggede på med en midlertidig kode tilsendt på e-mail.\nFor at afslutte indlogning skal du angive en ny adgangskode her:",
"resetpass-temp-password": "Midlertidig adgangskode",
"resetpass-abort-generic": "Ændring af adgangskode er blevet afbrudt af en udvidelse",
"resetpass-expired": "Din adgangskode er udløbet. Angiv en ny adgangskode for at logge på.",
- "resetpass-expired-soft": "Din adgangskode er udløbet og skal ændres. Vær venlig at ændre den nu, eller tryk \"{{int:authprovider-resetpass-skip-label}}\" for at ændre den senere.",
- "resetpass-validity-soft": "Din adgangskode er ikke gyldig: $1 \n\nVær venlig at ændre den nu, eller tryk \"{{int:authprovider-resetpass-skip-label}}\" for at ændre den senere.",
+ "resetpass-expired-soft": "Din adgangskode er udløbet og skal ændres. Ændr den venligst nu, eller tryk \"{{int:authprovider-resetpass-skip-label}}\" for at ændre den senere.",
+ "resetpass-validity-soft": "Din adgangskode er ikke gyldig: $1 \n\nVælg venligst en ny adgangskode nu, eller tryk \"{{int:authprovider-resetpass-skip-label}}\" for at ændre den senere.",
"passwordreset": "Nulstil adgangskode",
"passwordreset-text-one": "Udfyld denne formular for at nulstille din adgangskode.",
"passwordreset-text-many": "{{PLURAL:$1|Udfyld et af felterne for at modtage en midlertidig adgangskode via e-mail.}}",
"previewerrortext": "Der opstod en fejl under forsøget på at lave en forhåndsvisning af dine ændringer.",
"blockedtitle": "Du eller din IP-adresse er blokeret",
"blockedtext": "<strong>Dit brugernavn eller din IP-adresse er blevet blokeret.</strong>\n\nBlokeringen er foretaget af $1.\nDen anførte grund er <em>$2</em>.\n\nBlokeringen starter: $8\nBlokeringen udløber: $6\nBlokeringen er rettet mod: $7\n\nDu kan kontakte $1 eller en af de andre [[{{MediaWiki:Grouppage-sysop}}|administratorer]] for at diskutere blokeringen.\nDu kan ikke bruge funktionen \"{{int:emailuser}}\" medmindre der er angivet en gyldig e-mailadresse i dine [[Special:Preferences|kontoindstillinger]], og du ikke er blevet blokeret fra at bruge den.\n\nDin nuværende IP-adresse er $3, og blokerings-id er #$5.\nAngiv venligst alle ovenstående detaljer ved henvendelser om blokeringen.",
- "autoblockedtext": "Din IP-adresse er blevet blokeret automatisk fordi den blev brugt af en anden bruger som er blevet blokeret af $1.\nBegrundelsen for det er:\n\n:''$2''\n\n* Blokeringsperiodens start: $8\n* Blokeringen udløber: $6\n* Blokeringen er ment for: $7\n\nDu kan kontakte $1 eller en af de andre [[{{MediaWiki:Grouppage-sysop}}|administratorer]] for at diskutere blokeringen.\n\nBemærk at du ikke kan bruge funktionen \"e-mail til denne bruger\" medmindre du har en gyldig e-mailadresse registreret i din [[Special:Preferences|brugerindstilling]], og du ikke er blevet blokeret fra at bruge den.\n\nDin nuværende IP-adresse er $3, og blokerings-id'et er #$5.\nAngiv venligst alle de ovenstående detaljer ved eventuelle henvendelser.",
+ "autoblockedtext": "Din IP-adresse er blevet blokeret automatisk fordi den blev brugt af en anden bruger som er blevet blokeret af $1.\nDen givne begrundelse er:\n\n:<em>$2</em>\n\n* Blokeringsperiodens start: $8\n* Blokeringen udløber: $6\n* Blokeringen er rettet mod: $7\n\nDu kan kontakte $1 eller en af de andre [[{{MediaWiki:Grouppage-sysop}}|administratorer]] for at diskutere blokeringen.\n\nBemærk at du ikke kan bruge funktionen \"{{int:emailuser}}\" medmindre du har en gyldig e-mailadresse registreret i dine [[Special:Preferences|brugerindstillinger]] og du ikke er blevet blokeret fra at bruge den.\n\nDin nuværende IP-adresse er $3, og blokerings-id'et er #$5.\nAngiv venligst alle de ovenstående detaljer ved eventuelle henvendelser.",
"systemblockedtext": "Dit brugernavn eller din IP-adresse er automatisk blokeret af MediaWiki.\nBegrundelsen for det er:\n\n:<em>$2</em>\n\n* Blokeringsperiodens start: $8\n* Blokeringen udløber: $6\n* Blokeringen er ment for: $7\n\nDin nuværende IP-adresse er $3.\nAngiv venligst alle de ovenstående detaljer ved eventuelle henvendelser.",
"blockednoreason": "ingen begrundelse givet",
"whitelistedittext": "Du skal $1 for at kunne redigere sider.",
"readonlywarning": "<strong>Advarsel: Databasen er låst på grund af vedligeholdelse, så du kan ikke gemme dine ændringer lige nu.</strong>\nDet kan være en god idé at kopiere din tekst over i en tekstfil og gemme den til senere.\n\nAdministratoren, som låste databasen, gav denne forklaring: $1",
"protectedpagewarning": "'''ADVARSEL: Denne side er skrivebeskyttet, så kun administratorer kan redigere den.'''<br />\nDen seneste logpost vises nedenfor:",
"semiprotectedpagewarning": "'''Bemærk: Siden er låst, så kun registrerede brugere kan ændre den.'''\n<br />Den seneste logpost vises nedenfor:",
- "cascadeprotectedwarning": "<strong>Advarsel:</strong> Denne side er blevet beskyttet, så den kun kan ændres af brugere med administratorrettigheder, fordi indholdet er inkluderet i følgende {{PLURAL:$1|side|sider}} med nedarvet sidebeskyttelse:",
+ "cascadeprotectedwarning": "<strong>Advarsel:</strong> Denne side er blevet beskyttet, så kun brugere med [[Special:ListGroupRights|bestemte rettigheder]] kan ændre den, fordi indholdet er inkluderet i følgende {{PLURAL:$1|side|sider}} med nedarvet sidebeskyttelse:",
"titleprotectedwarning": "ADVARSEL: Den side er låst så kun [[Special:ListGroupRights|visse brugere]] kan oprette den.'''\n<br />Den seneste logpost vises nedenfor:",
"templatesused": "{{PLURAL:$1|Skabelon|Skabeloner}} der er brugt på denne side:",
"templatesusedpreview": "Følgende {{PLURAL:$1|skabelon|skabeloner}} bruges i denne forhåndsvisning:",
"recentchangesdays": "Antal dage som skal vises i seneste ændringer:",
"recentchangesdays-max": "(maks. $1 {{PLURAL:$1|dag|dage}})",
"recentchangescount": "Antal redigeringer som skal vises som standard i sidste ændringer, sidehistorikker og logger:",
- "prefs-help-recentchangescount": "Det gælder for seneste ændringer, historikker og logger.",
+ "prefs-help-recentchangescount": "Maksimalt antal: 1000",
"prefs-help-watchlist-token2": "Dette er den hemmelige nøgle til web-feed af din overvågningsliste.\nHvis andre kender den, vil man være i stand til at læse din overvågningsliste, så del den ikke.\n[[Special:ResetTokens|Klik her]] hvis du har brug at nulstille den.",
"savedprefs": "Dine indstillinger er blevet gemt.",
"savedrights": "Brugergrupperne for {{GENDER:$1|$1}} er blevet gemt.",
"uploadstash-summary": "Denne side giver adgang til filer, de er uploadet (eller er i gang med at blive det), men som endnu ikke er offentliggjort på wikien. Disse filer er kun synlige for brugeren, der har uploadet dem.",
"uploadstash-clear": "Ryd stashede filer",
"uploadstash-nofiles": "Du har ingen stashede filer.",
- "uploadstash-badtoken": "Udførelse af handlingen mislykkedes, måske fordi dine redigerings legitimationsoplysninger udløbet. Prøv igen.",
+ "uploadstash-badtoken": "Udførelsen af handlingen mislykkedes, måske fordi dine legitimationsoplysninger for redigering er udløbet. Prøv venligst igen.",
"uploadstash-errclear": "Rydning af filerne mislykkedes.",
"uploadstash-refresh": "Opdatér filoversigten",
"uploadstash-thumbnail": "vis miniature",
"filehist-filesize": "Filstørrelse",
"filehist-comment": "Kommentar",
"imagelinks": "Filanvendelse",
- "linkstoimage": "{{PLURAL:$1|Den følgende side|De følgende $1 sider}} henviser til denne fil:",
+ "linkstoimage": "{{PLURAL:$1|Den følgende side|De følgende $1 sider}} bruger denne fil:",
"linkstoimage-more": "Flere end $1 {{PLURAL:$1|side|sider}} henviser til denne fil.\nDen følgende liste viser kun {{PLURAL:$1|den første henvisning|de $1 første henvisninger}}.\nEn [[Special:WhatLinksHere/$2|komplet liste]] er tilgængelig.",
- "nolinkstoimage": "Der er ingen sider der henviser til denne fil.",
+ "nolinkstoimage": "Der er ingen sider der bruger denne fil.",
"morelinkstoimage": "Se [[Special:WhatLinksHere/$1|flere henvisninger]] til denne fil.",
"linkstoimage-redirect": "$1 (filomdirigering) $2",
"duplicatesoffile": "Følgende {{PLURAL:$1|fil er en dublet|filer er dubletter}} af denne fil ([[Special:FileDuplicateSearch/$2|flere detaljer]]):",
"editcomment": "Redigeringsbeskrivelsen var: <em>$1</em>.",
"revertpage": "Gendannet til seneste version af [[User:$1|$1]], fjerner ændringer fra [[Special:Contributions/$2|$2]] ([[User talk:$2|diskussion]])",
"revertpage-nouser": "Gendannet til seneste version af {{GENDER:$1|[[User:$1|$1]]}}, fjerner ændringer fra en skjult bruger",
- "rollback-success": "Ændringerne fra $1 er fjernet,\nog den seneste version af $2 er gendannet.",
+ "rollback-success": "Ændringerne foretaget af {{GENDER:$3|$1}} er blevet tilbagestillet, og den seneste version af {{GENDER:$4|$2}} er gendannet.",
"sessionfailure-title": "Sessionsfejl",
- "sessionfailure": "Der lader til at være et problem med din loginsession; denne handling blev annulleret som en sikkerhedsforanstaltning mod kapring af sessionen. Tryk på \"tilbage\"-knappen og genindlæs den side du kom fra, og prøv dernæst igen.",
+ "sessionfailure": "Der lader til at være et problem med din loginsession; denne handling blev annulleret som en sikkerhedsforanstaltning mod kapring af sessionen. Genindsend venligst formularen.",
"changecontentmodel-legend": "Ændr indholdsmodel",
"changecontentmodel-title-label": "Sidetitel",
"changecontentmodel-model-label": "Ny indholdsmodel",
"sp-contributions-newbies-sub": "Fra nye kontoer",
"sp-contributions-newbies-title": "Brugerbidrag fra nye konti",
"sp-contributions-blocklog": "blokeringslog",
- "sp-contributions-suppresslog": "undertrykte brugerbidrag",
+ "sp-contributions-suppresslog": "undertrykte {{GENDER:$1|brugerbidrag}}",
"sp-contributions-deleted": "slettede {{GENDER:$1|brugerbidrag}}",
"sp-contributions-uploads": "uploads",
"sp-contributions-logs": "loglister",
"sp-contributions-talk": "diskussion",
- "sp-contributions-userrights": "håndtering af brugerrettigheder",
+ "sp-contributions-userrights": "håndtering af {{GENDER:$1|brugerrettigheder}}",
"sp-contributions-blocked-notice": "Denne bruger er i øjeblikket blokeret. Loggen over den seneste blokering kan ses nedenfor:",
"sp-contributions-blocked-notice-anon": "Denne IP-adresse er i øjeblikket blokeret.\nDen seneste post i blokeringsloggen vises nedenfor:",
"sp-contributions-search": "Søg efter bidrag",
"lockedbyandtime": "(af $1 den $2 kl. $3)",
"move-page": "Flyt $1",
"move-page-legend": "Flyt side",
- "movepagetext": "Når du bruger formularen herunder, vil du få omdøbt en side og flyttet hele sidens historie til det nye navn.\nDen gamle titel vil blive en omdirigeringsside til den nye titel.\nDu kan opdatere omdirigeringer, der peger på den oprindelige titel, automatisk.\nHvis du vælger ikke at opdatere dem automatisk, så sørg for at tjekke efter [[Special:DoubleRedirects|dobbelte]] eller [[Special:BrokenRedirects|dårlige omdirigeringer]].\nDu er ansvarlig for, at alle henvisninger stadig peger derhen, hvor det er meningen de skal pege.\n\nBemærk at siden '''ikke''' kan flyttes, hvis der allerede er en side med den nye titel, medmindre den side er en omdirigering uden nogen redigeringshistorik.\nDet betyder, at du kan flytte en side tilbage hvor den kom fra, hvis du kommer til at lave en fejl, og det betyder, at du ikke kan overskrive en eksisterende side.\n\n'''ADVARSEL!'''\nDette kan være en drastisk og uventet ændring for en populær side; vær sikker på, at du forstår konsekvenserne af dette før du fortsætter.",
- "movepagetext-noredirectfixer": "Brug formularen herunder du vil omdøbe en side og flyttet hele sidens historie til det nye navn.\nDen gamle titel vil blive en omdirigeringsside til den nye titel.\nVær sikker på at tjekke for [[Special:DoubleRedirects|dobbelte]] eller [[Special:BrokenRedirects|ødelagte omdirigeringer]].\nDu er ansvarlig for at sikre, at alle henvisninger stadig peger på et sted hvor det giver meningen at gå.\n\nBemærk, at siden '''ikke''' kan flyttes hvis der allerede er en side med den nye titel, medmindre den er tom eller er en omdirigering, og har ingen historie.\nDet betyder at du kan omdøbe en side tilbage hvor den kom fra, hvis du laver en fejl, og du kan ikke overskrive en eksisterende side.\n\n'''Advarsel!'''\nDette kan være en drastisk og uventet ændring for en populær side;\ndu skal være sikker på at du forstår konsekvenserne af dette før du fortsætter.",
+ "movepagetext": "Når du bruger formularen herunder, vil du få omdøbt en side og flyttet hele sidens historie til det nye navn.\nDen gamle titel vil blive en omdirigeringsside til den nye titel.\nDu kan opdatere omdirigeringer, der peger på den oprindelige titel, automatisk.\nHvis du vælger ikke at opdatere dem automatisk, så sørg for at tjekke efter [[Special:DoubleRedirects|dobbelte]] eller [[Special:BrokenRedirects|ødelagte omdirigeringer]].\nDu er ansvarlig for, at alle henvisninger stadig peger derhen, hvor det er meningen de skal pege.\n\nBemærk at siden <strong>ikke</strong> kan flyttes, hvis der allerede er en side med den nye titel, medmindre den side er en omdirigering uden nogen redigeringshistorik.\nDet betyder, at du kan flytte en side tilbage hvor den kom fra, hvis du kommer til at lave en fejl, og det betyder, at du ikke kan overskrive en eksisterende side.\n\n<strong>Bemærk:</strong>\nDette kan være en drastisk og uventet ændring for en populær side; vær sikker på, at du forstår konsekvenserne af dette før du fortsætter.",
+ "movepagetext-noredirectfixer": "Brug af formularen herunder vil omdøbe en side og flytte hele sidens historie til det nye navn.\nDen gamle titel vil blive en omdirigeringsside til den nye titel.\nVær sikker på at tjekke for [[Special:DoubleRedirects|dobbelte]] eller [[Special:BrokenRedirects|ødelagte omdirigeringer]].\nDu er ansvarlig for at sikre, at alle henvisninger stadig peger på det, som det er meningen, de skal pege på.\n\nBemærk, at siden <strong>ikke</strong> kan flyttes, hvis der allerede er en side med den nye titel, medmindre det er en omdirigeringsside uden historie.\nDet betyder, at du kan omdøbe en side tilbage hvor den kom fra, hvis du laver en fejl, og at du ikke kan overskrive en eksisterende side.\n\n<strong>Bemærk:</strong>\nDette kan være en drastisk og uventet ændring for en populær side; vær sikker på, at du forstår konsekvenserne af dette før du fortsætter.",
"movepagetalktext": "Den tilhørende diskussionsside, hvis der er en, vil automatisk blive flyttet med siden '''medmindre:''' *Du flytter siden til et andet navnerum,\n*En ikke-tom diskussionsside allerede eksisterer under det nye navn, eller\n*Du fjerner markeringen i boksen nedenunder.\n\nI disse tilfælde er du nødt til at flytte eller sammenflette siden manuelt.",
"moveuserpage-warning": "'''Advarsel:''' Du er ved at flytte en brugerside. Bemærk at det kun er siden, der vil blive flyttet – brugeren bliver ''ikke'' omdøbt.",
"movenologintext": "Du skal være registreret bruger og [[Special:UserLogin|logget på]] for at flytte en side.",
"delete_and_move_text": "==Sletning nødvendig==\n\nArtiklen \"[[:$1]]\" eksisterer allerede. Vil du slette den for at gøre plads til flytningen?",
"delete_and_move_confirm": "Ja, slet siden",
"delete_and_move_reason": "Slettet for at gøre plads til flytning fra \"[[$1]]\"",
- "selfmove": "Begge sider har samme navn. Man kan ikke flytte en side oven i sig selv.",
+ "selfmove": "Titlen er den samme; man kan ikke flytte en side til samme side.",
"immobile-source-namespace": "Kan ikke flytte sider i navnerummet \"$1\"",
"immobile-target-namespace": "Kan ikke flytte sider til navnerummet \"$1\"",
"immobile-target-namespace-iw": "En side kan ikke flyttes til en interwiki-henvisning.",
"fix-double-redirects": "Opdater henvisninger til det oprindelige navn",
"move-leave-redirect": "Efterlad en omdirigering",
"protectedpagemovewarning": "'''Bemærk:''' Denne side er låst så kun administratorer kan flytte den.<br />\nDen seneste logpost vises nedenfor:",
- "semiprotectedpagemovewarning": "'''Bemærk:''' Denne side er låst så kun registrerede brugere kan flytte den.<br />\nDen seneste logpost vises nedenfor:",
+ "semiprotectedpagemovewarning": "<strong>Bemærk:</strong> Denne side er låst, så kun automatisk bekræftede brugere kan flytte den.\nDen seneste logpost vises nedenfor som reference:",
"move-over-sharedrepo": "== Fil findes ==\n[[:$1]] findes på en delt kilde. Ved at flytte en fil til denne titel vil overskrive den eksisterende delte fil.",
"file-exists-sharedrepo": "Det valgte filnavn er allerede i brug på en delt kilde.\nVælg venligst et andet navn.",
"export": "Eksportér sider",
"confirm-unwatch-top": "Diese Seite von der persönlichen Beobachtungsliste entfernen?",
"confirm-rollback-button": "Okay",
"confirm-rollback-top": "Bearbeitungen an dieser Seite zurücksetzen?",
+ "confirm-mcrundo-title": "Eine Änderung rückgängig machen",
+ "mcrundofailed": "Rückgängigmachung fehlgeschlagen",
+ "mcrundo-missingparam": "Erforderliche Parameter fehlen bei der Anfrage.",
+ "mcrundo-changed": "Die Seite wurde verändert, seit du dir den Versionsunterschied ansiehst. Bitte überprüfe die neue Änderung.",
"ellipsis": "…",
"percent": "$1 %",
"quotation-marks": "„$1“",
"edit-error-long": "Fehler:\n\n$1",
"revid": "Version $1",
"pageid": "Seitenkennung $1",
- "interfaceadmin-info": "$1\n\nBerechtigungen für das Bearbeiten von wikiweiten CSS/JS/JSON-Dateien wurden kürzlich von dem Recht <code>editinterface</code> getrennt. Falls du nicht verstehst, warum du diesen Fehler erhältst, siehe bitte [[mw:MediaWiki_1.32/interface-admin]].",
+ "interfaceadmin-info": "$1\n\nBerechtigungen für das Bearbeiten von wikiweiten CSS/JS/JSON-Dateien wurden kürzlich vom Recht <code>editinterface</code> getrennt. Falls du nicht verstehst, warum du diesen Fehler erhältst, siehe [[mw:MediaWiki_1.32/interface-admin]].",
"rawhtml-notallowed": "<html>-Tags können nicht außerhalb von normalen Seiten verwendet werden.",
"gotointerwiki": "{{SITENAME}} verlassen",
"gotointerwiki-invalid": "Der angegebene Titel ist ungültig.",
"cascadeprotected": "Za toś ten bok jo se wobźěłowanje znjemóžniło, dokulaž jo zawězany do {{PLURAL:$1|slědujucego boka|slědujuceju bokowu|slědujucych bokow}}, {{PLURAL:$1|kótaryž jo|kótarejž stej|kótarež su}} pśez kaskadowu opciju {{PLURAL:$1|šćitany|šćitanej|šćitane}}: $2",
"namespaceprotected": "Njejsy wopšawnjony, boki w rumje: '''$1''' wobźěłaś.",
"customcssprotected": "Njamaš pšawo, aby toś ten CSS-bok wobźěłał, dokulaž wopśimujo wósobinske nastajenja drugego wužywarja.",
+ "customjsonprotected": "Njamaš pšawo, aby toś ten JSON-bok wobźěłał, dokulaž wopśimujo wósobinske nastajenja drugego wužywarja.",
"customjsprotected": "Njamaš pšawo, aby toś ten JavaScriptowy bok wobźěłał, dokulaž wopśimujo wósobinske nastajenja drugego wužywarja.",
"mycustomcssprotected": "Njamaš pšawo toś ten CSS-bok wobźěłaś.",
+ "mycustomjsonprotected": "Njamaš pšawo toś ten JSON-bok wobźěłaś.",
"mycustomjsprotected": "Njamaš pšawo toś ten JavaScript-bok wobźěłaś.",
"myprivateinfoprotected": "Njamaš pšawo swóje priwatne informacije wobźěłaś.",
"mypreferencesprotected": "Njamaš pšawo swóje nastajenja wobźěłaś.",
"confirm-unwatch-top": "Remove this page from your watchlist?",
"confirm-rollback-button": "OK",
"confirm-rollback-top": "Revert edits to this page?",
+ "confirm-mcrundo-title": "Undo a change",
+ "mcrundofailed": "Undo failed",
+ "mcrundo-missingparam": "Missing required parameters on request.",
+ "mcrundo-changed": "The page has been changed since you viewed the diff. Please review the new change.",
"semicolon-separator": "; ",
"comma-separator": ", ",
"colon-separator": ": ",
"Amaia",
"Tiberius1701",
"Astroemi",
- "Jelou"
+ "Jelou",
+ "Ktranz"
]
},
"tog-underline": "Subrayar los enlaces:",
"ns-specialprotected": "No se pueden editar las páginas especiales.",
"titleprotected": "Este título ha sido protegido contra creación por [[User:$1|$1]].\nEl motivo proporcionado es <em>$2</em>.",
"filereadonlyerror": "No se puede modificar el archivo \"$1\" porque el repositorio de archivos \"$2\" es de solo lectura.\n\nEl administrador del sistema que lo ha bloqueado ofrece esta explicación: \"$3\".",
+ "invalidtitle": "Título inválido",
"invalidtitle-knownnamespace": "El título con el espacio de nombres «$2» y el texto «$3» no es válido",
"invalidtitle-unknownnamespace": "El título con el espacio de nombres desconocido (n.º $1) y el texto «$2» no es válido",
"exception-nologin": "No has accedido",
"uploadstash-zero-length": "El archivo está vacío.",
"invalid-chunk-offset": "Desplazamiento inválido del fragmento",
"img-auth-accessdenied": "Acceso denegado",
- "img-auth-nopathinfo": "Falta PATH_INFO.\nEl servidor no está configurado para proporcionar esta información.\nEs posible que esté basado en CGI y que no sea compatible con img_auth.\nConsulte https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
+ "img-auth-nopathinfo": "Falta la información de ruta.\nEl servidor tiene que estar configurado para proporcionar las variables REQUEST_URI y/o PATH_INFO.\nSi lo está, intentá habilitar $wgUsePathInfo.\nConsulte https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
"img-auth-notindir": "La ruta solicitada no figura en la carpeta de subidas configurada.",
"img-auth-badtitle": "Incapaz de construir un título válido de «$1».",
"img-auth-nologinnWL": "No has iniciado sesión y «$1» no está en la lista blanca.",
"filehist-filesize": "Tamaño del archivo",
"filehist-comment": "Comentario",
"imagelinks": "Usos del archivo",
- "linkstoimage": "{{PLURAL:$1|La siguiente página enlaza|Las siguientes páginas enlazan}} a este archivo:",
- "linkstoimage-more": "Hay más de {{PLURAL:$1|una página que enlaza|$1 páginas que enlazan}} con este archivo.\nLa lista siguiente sólo muestra {{PLURAL:$1|la primera página que enlaza|las primeras $1 páginas que enlazan}} con este archivo.\nTambién puedes consultar la [[Special:WhatLinksHere/$2|lista completa]].",
- "nolinkstoimage": "No hay páginas que enlacen a esta imagen.",
+ "linkstoimage": "{{PLURAL:$1|La siguiente página usa|Las siguientes páginas usan}} a este archivo:",
+ "linkstoimage-more": "Hay más de {{PLURAL:$1|una página que usa|$1 páginas que usan}} este archivo.\nLa lista siguiente sólo muestra {{PLURAL:$1|la primera página que usa|las primeras $1 páginas que usan}} este archivo.\nTambién puedes consultar la [[Special:WhatLinksHere/$2|lista completa]].",
+ "nolinkstoimage": "No hay páginas que enlacen a este archivo.",
"morelinkstoimage": "Mira [[Special:WhatLinksHere/$1|más enlaces]] a este archivo.",
"linkstoimage-redirect": "$1 (archivo de redirección) $2",
"duplicatesoffile": "{{PLURAL:$1|El siguiente archivo es un duplicado|Los siguientes $1 archivos son duplicados}} de éste ([[Special:FileDuplicateSearch/$2|más detalles]]):",
"confirm-unwatch-top": "¿Quitar esta página de tu lista de seguimiento?",
"confirm-rollback-button": "Aceptar",
"confirm-rollback-top": "¿Revertir las ediciones a esta página?",
+ "confirm-mcrundo-title": "Deshacer un cambio",
+ "mcrundofailed": "Error al deshacer",
+ "mcrundo-missingparam": "Faltan parámetros requeridos en la solicitud.",
"comma-separator": ", ",
"ellipsis": "…",
"percent": "$1 %",
"limitreport-expansiondepth": "Profundidad máxima de expansión",
"limitreport-expensivefunctioncount": "Cuenta de la función expansiva del analizador",
"limitreport-unstrip-depth": "Profundidad de recursión de función «unstrip»",
+ "limitreport-unstrip-size": "Unstrip tamaño post-expandido",
"limitreport-unstrip-size-value": "$1/$2 {{PLURAL:$2|byte|bytes}}",
"expandtemplates": "Expandir plantillas",
"expand_templates_intro": "Esta página especial toma un texto wiki y expande todas sus plantillas recursivamente.\nTambién expande las funciones sintácticas como <code><nowiki>{{</nowiki>#language:…}}</code>, y variables como\n<code><nowiki>{{</nowiki>CURRENTDAY}}</code>. De hecho, expande casi cualquier cosa que esté entre llaves dobles.",
"passwordpolicies-policy-passwordcannotmatchusername": "La contraseña no puede ser la misma que el nombre de usuario",
"passwordpolicies-policy-passwordcannotmatchblacklist": "La contraseña no puede coincidir con la lista de contraseñas específicamente prohibidas",
"passwordpolicies-policy-maximalpasswordlength": "La contraseña no puede tener más de $1 {{PLURAL:$1|caracter|caracteres}}",
- "passwordpolicies-policy-passwordcannotbepopular": "La contraseña no puede {{PLURAL:$1|ser la contraseña más popular|encontrarse en la lista de $1 contraseñas populares}}"
+ "passwordpolicies-policy-passwordcannotbepopular": "La contraseña no puede {{PLURAL:$1|ser la contraseña más popular|encontrarse en la lista de $1 contraseñas populares}}",
+ "easydeflate-invaliddeflate": "El contenido proporcionado no esta comprimido correctamente"
}
"ns-specialprotected": "صفحههای ویژه غیر قابل ویرایش هستند.",
"titleprotected": "این عنوان توسط [[User:$1|$1]] در برابر ایجاد محافظت شدهاست.\nدلیل ارائهشده این است: <em>$2</em>.",
"filereadonlyerror": "تغییر پروندهٔ «$1» ممکن نیست چون مخزن پروندهٔ «$2» در حالت فقط خواندنی قرار دارد.\n\nمدیری که آن را قفل کرده چنین توضیحی را ذکر کرده: «$3».",
+ "invalidtitle": "عنوان نامعتبر",
"invalidtitle-knownnamespace": "عنوان نامعتبر با فضای نام «$2» و متن «$3»",
"invalidtitle-unknownnamespace": "عنوان نامعتبر با فضای نام ناشناختهٔ شمارهٔ $1 و متن «$2»",
"exception-nologin": "به سامانه وارد نشدهاید",
"converter-manual-rule-error": "خطا در قوانین مبدل دستی زبان",
"undo-success": "این ویرایش را میتوان خنثی کرد.\nلطفاً تفاوت زیر را بررسی کنید تا تأیید کنید که این چیزی است که میخواهید انجام دهید، سپس تغییرات زیر را ذخیره کنید تا خنثیسازی ویرایش را به پایان ببرید.",
"undo-failure": "به علت تعارض با ویرایشهای میانی، این ویرایش را نمیتوان خنثی کرد.",
+ "undo-main-slot-only": "ویرایش را نمیتوان انجام داد زیرا شامل محتویات خارج از شیار اصلی است.",
"undo-norev": "این ویرایش را نمیتوان خنثی کرد چون وجود ندارد یا حذف شدهاست.",
"undo-nochange": "به نظر میرسد ویرایش از پیش خنثیسازی شده است.",
"undo-summary": "خنثیسازی ویرایش $1 توسط [[Special:Contributions/$2|$2]] ([[User talk:$2|بحث]])",
"diff-paragraph-moved-toold": "پاراگراف جابهجا شده بود. کلیک کنید تا به جای قدیمش بروید.",
"difference-missing-revision": "{{PLURAL:$2|یک ویرایش|$2 ویرایش}} از تفاوت نسخهها ($1) {{PLURAL:$2|یافت|یافت}} نشد.\n\nاین اتفاق معمولاً در اثر دنبال کردن پیوند تفاوتی به یک صفحهٔ حذفشده پیش میآید.\nمیتوانید جزئیات بیشتر را در [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} سیاههٔ حذف] بیابید.",
"searchresults": "نتایج جستجو",
+ "search-filter-title-prefix": "فقط در صفحاتی که عنوانش با «$1» شروع میشود",
"search-filter-title-prefix-reset": "جستجوی همه صفحات",
"searchresults-title": "نتایج جستجو برای «$1»",
"titlematches": "تطبیق عنوان مقاله",
"prefs-watchlist-edits": "تعداد ویرایشهای نشاندادهشده در فهرست پیگیریها:",
"prefs-watchlist-edits-max": "حداکثر تعداد: ۱۰۰۰",
"prefs-watchlist-token": "رمز فهرست پیگیری:",
+ "prefs-watchlist-managetokens": "مدیریت بلیطها",
"prefs-misc": "متفرقه",
"prefs-resetpass": "تغییر گذرواژه",
"prefs-changeemail": "تغییر یا حذف نشانی ایمیل",
"recentchangescount": "تعداد نمایش پیشفرض ویرایشها در تغییرات اخیر، تاریخچه صفحه و سیاههها:",
"prefs-help-recentchangescount": "حداکثر تعداد: ۱۰۰۰",
"prefs-help-watchlist-token2": "این کلید رمز خوراک وب فهرست پیگیریهای شماست.\nهرکس آن را بداند میتواند فهرست پیگیریهایتان را بخواند، بنابراین آن را به اشتراک نگذارید. اگر لازم باشد [[Special:ResetTokens|میتوانید کلیدی نو ایجاد کنید]].",
+ "prefs-help-tokenmanagement": "برای حسابتان که به خوراک وبسایت فهرست پیگیریتان دسترسی دارد کلید محرمانه را میتوانید ببینید و بازنشانی کنید. کلید قادر به خواندن فهرست پیگیریهای شما خواهد بود، پس آن را به اشتراک نگذارید.",
"savedprefs": "ترجیحات شما ذخیره شد.",
"savedrights": "گروههای کاربری {{GENDER:$1|$1}} ذخیره شدهاست.",
"timezonelegend": "منطقهٔ زمانی:",
"group-autoconfirmed-member": "{{GENDER:$1|کاربر تأییدشده}}",
"group-bot-member": "ربات",
"group-sysop-member": "{{GENDER:$1|مدیر}}",
- "group-interface-admin-member": "مدیر رابط کاربری",
+ "group-interface-admin-member": "{{GENDER:$1|مدیر رابط کاربری}}",
"group-bureaucrat-member": "{{GENDER:$1|دیوانسالار}}",
"group-suppress-member": "{{GENDER:$1|فرونشاننده}}",
"grouppage-user": "{{ns:project}}:کاربران",
"grouppage-autoconfirmed": "{{ns:project}}:کاربران تأییدشده",
"grouppage-bot": "{{ns:project}}:رباتها",
"grouppage-sysop": "{{ns:project}}:مدیران",
+ "grouppage-interface-admin": "{{ns:project}}:مدیران رابط کاربری",
"grouppage-bureaucrat": "{{ns:project}}:دیوانسالاران",
"grouppage-suppress": "{{ns:project}}:فرونشانی",
"right-read": "خواندن صفحه",
"right-editcontentmodel": "ویرایش مدل محتوای یک صفحه",
"right-editinterface": "ویرایش واسط کاربری",
"right-editusercss": "ویرایش صفحههای CSS دیگر کاربرها",
+ "right-edituserjson": "ویرایش پروندههای JSON دیگر کاربرها",
"right-edituserjs": "ویرایش صفحههای JS دیگر کاربرها",
+ "right-editsitecss": "ویرایش گسترده CSS وبگاه",
+ "right-editsitejson": "ویرایش گسترده JSON وبگاه",
+ "right-editsitejs": "ویرایش گسترده JavaScript وبگاه",
"right-editmyusercss": "پروندههای سیاساس کاربری خود را ویرایش کنید",
+ "right-editmyuserjson": "پروندههای JSON کاربری خود را ویرایش کنید",
"right-editmyuserjs": "پروندههای جاوااسکریپت کاربری خود را ویرایش کنید",
"right-viewmywatchlist": "فهرست پیگیریهای خود را ببینید",
"right-editmywatchlist": "فهرست پیگیریهای خود را ویرایش کنید. توجه داشته باشید برخی از اقدامات حتی بدون این دسترسی هم صفحات را اضافه میکنند.",
"grant-createaccount": "ایجاد حسابهای کاربری",
"grant-createeditmovepage": "ایجاد، ویرایش و انتقال صفحات",
"grant-delete": "حذف صفحات، نسخههای ویرایش و سیاهه ورودی",
- "grant-editinterface": "ویرایش CSS کاربر/جاوااسکریپت/JSON و فضای نام مدیاویکی",
+ "grant-editinterface": "ویرایش صفحههای جیسان کاربری یا سراسری و فضای نام مدیاویکی",
"grant-editmycssjs": "ویرایش CSS /جاوااسکریپت/JSON کاربری",
"grant-editmyoptions": "اولویتهای کاربری را ویرایش کنید",
"grant-editmywatchlist": "ویرایش فهرست پیگیریهایتان",
+ "grant-editsiteconfig": "ویرایش گسترده CSS/JS کاربر",
"grant-editpage": "ویرایش صفحات موجود",
"grant-editprotected": "ویرایش صفحه محافظت شده",
"grant-highvolume": "ویرایش با حجم بالا",
"rcfilters-activefilters": "پالایههای فعال",
"rcfilters-activefilters-hide": "نهفتن",
"rcfilters-activefilters-show": "نمایش",
+ "rcfilters-activefilters-hide-tooltip": "پنهان کردن محیط پالایه فعال",
+ "rcfilters-activefilters-show-tooltip": "نمایش محیط پالایه فعال",
"rcfilters-advancedfilters": "پالایههای پیشرفته",
"rcfilters-limit-title": "تعداد تغییرات برای نمایش",
"rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|تغییر|تغییر}}, $2",
"rcfilters-filter-humans-label": "انسان (ربات نه)",
"rcfilters-filter-humans-description": "ویرایش توسط انسان.",
"rcfilters-filtergroup-reviewstatus": "وضعیت بازبینی",
+ "rcfilters-filter-reviewstatus-unpatrolled-description": "ویرایشهای غیردستی یا خودکار به عنوان گشتخورده.",
"rcfilters-filter-reviewstatus-unpatrolled-label": "گشتنخورده",
+ "rcfilters-filter-reviewstatus-manual-description": "ویرایشهای دستی به عنوان گشتخورده.",
"rcfilters-filter-reviewstatus-manual-label": "به طور دستی گشت خورد",
+ "rcfilters-filter-reviewstatus-auto-description": "ویرایشهای کاربران باتجربه که ویرایشش به عنوان گشتخورده برچسب خوردهاست.",
+ "rcfilters-filter-reviewstatus-auto-label": "گشت خودکار",
"rcfilters-filtergroup-significance": "اهمیت",
"rcfilters-filter-minor-label": "ویرایشهای جزئی",
"rcfilters-filter-minor-description": "ویرایشهایی که به عنوان جزئی برچسب خوردهاند.",
"rcfilters-watchlist-showupdated": "تغییرات صفحاتی که شما بازدید نکردید از زمانی که تغییرات رخ داده به صورت <strong>پررنگ</strong>، با نشانگر توپر.",
"rcfilters-preference-label": "مخفی کردن نسخه بهبود یافته تغییرات اخیر",
"rcfilters-preference-help": "تغییرات رابط کاربری که در سال ۲۰۱۷ اضافه شده است را بر میگرداند.",
+ "rcfilters-watchlist-preference-label": "نمایش نسخهٔ بهبودیافتهٔ فهرست پیگیری",
+ "rcfilters-watchlist-preference-help": "واگردان در سال ۲۰۱۷ دوباره طراحی شد و تمام ابزارها اضافه و از آن زمان به بعد اضافه شدند.",
"rcfilters-filter-showlinkedfrom-label": "نمایش تغییرات صفحاتی که پیوند شدهاند",
"rcfilters-filter-showlinkedfrom-option-label": "<strong>صفحات پیوند به</strong> صفحهٔ انتخاب شده",
"rcfilters-filter-showlinkedto-label": "نمایش تغییرات در صفحاتی که در ون این صفحه پیوند شدهاند",
"uploadstash-zero-length": "اندازهٔ پرونده صفر است.",
"invalid-chunk-offset": "جابجایی نامعتبر قطعه",
"img-auth-accessdenied": "منع دسترسی",
- "img-auth-nopathinfo": "PATH_INFO موجود نیست.\nسرور شما برای ردکردن این مقدار تنظیم نشدهاست.\nممکن است مبتنی بر سیجیآی باشد و از img_auth پشتیبانی نکند.\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization را ببینید.",
+ "img-auth-nopathinfo": "مسیر اطلاعات موجود نیست.\nسرورتان برای ردکردن متغییرهای REQUEST_URI و/یا PATH_INFO باید تنظیم شود.\nاگر مبتنی قصد فعالکردن wgUsePathInfo دارد.\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization را ببینید.",
"img-auth-notindir": "مسیر درخواست شده در شاخهٔ بارگذاری تنظیمشده قرار ندارد.",
"img-auth-badtitle": "امکان ایجاد یک عنوان مجاز از «$1» وجود ندارد.",
"img-auth-nologinnWL": "شما به سامانه وارد نشدهاید و «$1» در فهرست سفید قرار ندارد.",
"http-timed-out": "مهلت درخواست اچتیتیپی به سر رسید.",
"http-curl-error": "خطا در آوردن نشانی اینترنتی: $1",
"http-bad-status": "در حین درخواست اچتیتیپی خطایی رخ داد: $1 $2",
+ "http-internal-error": "خطای درونی HTTP",
"upload-curl-error6": "دسترسی به نشانی اینترنتی ممکن نشد",
"upload-curl-error6-text": "نشانی اینترنتی داده شده قابل دسترسی نیست.\nلطفاً درستی آن و اینکه تارنما برقرار است را بررسی کنید.",
"upload-curl-error28": "مهلت بارگذاری به سر رسید",
"filehist-filesize": "اندازهٔ پرونده",
"filehist-comment": "توضیح",
"imagelinks": "کاربرد پرونده",
- "linkstoimage": "{{PLURAL:$1|صفحهٔ|صفحههای}} زیر به این تصویر پیوند {{PLURAL:$1|دارد|دارند}}:",
- "linkstoimage-more": "بÛ\8cØ´ از $1 صÙ\81ØÙ\87 بÙ\87 اÛ\8cÙ\86 پرÙ\88Ù\86دÙ\87 Ù¾Û\8cÙ\88Ù\86د {{PLURAL:$1|دارد|دارÙ\86د}}.\nÙ\81Ù\87رست زÛ\8cر تÙ\86Ù\87ا {{PLURAL:$1|اÙ\88Ù\84Û\8cÙ\86 Ù¾Û\8cÙ\88Ù\86د|اÙ\88Ù\84Û\8cÙ\86 $1 Ù¾Û\8cÙ\88Ù\86د}} بÙ\87 این صفحه را نشان میدهد.\n[[Special:WhatLinksHere/$2|فهرست کامل]] نیز موجود است.",
+ "linkstoimage": "{{PLURAL:$1|صفحهٔ|صفحههای}} زیر به این تصویر پیوند دارد:",
+ "linkstoimage-more": "بÛ\8cØ´ از $1 صÙ\81ØÙ\87 از اÛ\8cÙ\86 پرÙ\88Ù\86دÙ\87 استÙ\81ادÙ\87 {{PLURAL:$1|Ù\85Û\8câ\80\8cÚ©Ù\86د|Ù\85Û\8câ\80\8cÚ©Ù\86Ù\86د}}.\nÙ\81Ù\87رست زÛ\8cر تÙ\86Ù\87ا {{PLURAL:$1|اÙ\88Ù\84Û\8cÙ\86 استÙ\81ادÙ\87|اÙ\88Ù\84Û\8cÙ\86 $1 استÙ\81ادÙ\87}} از این صفحه را نشان میدهد.\n[[Special:WhatLinksHere/$2|فهرست کامل]] نیز موجود است.",
"nolinkstoimage": "این پرونده در هیچ صفحهای به کار نرفتهاست.",
"morelinkstoimage": "[[Special:WhatLinksHere/$1|پیوندهای دیگر]] به این پرونده را ببینید.",
"linkstoimage-redirect": "$1 (تغییرمسیر پرونده) $2",
"protectedtitles-submit": "نمایش عناوین",
"listusers": "فهرست کاربران",
"listusers-editsonly": "فقط کاربرانی که ویرایش دارند را نشان بده",
+ "listusers-temporarygroupsonly": "نمایش کاربرانی که به صورت موقت در گروه کاربران هستند",
"listusers-creationsort": "مرتب کردن بر اساس تاریخ ایجاد",
"listusers-desc": "ترتیب نزولی",
"usereditcount": "$1 {{PLURAL:$1|ویرایش|ویرایش}}",
"apisandbox-dynamic-parameters-add-label": "افزودن پارامتر:",
"apisandbox-dynamic-parameters-add-placeholder": "نام پارامتر",
"apisandbox-dynamic-error-exists": "پارامتری به نام \"$1\"هم اکنون وجود دارد.",
+ "apisandbox-templated-parameter-reason": "این [[Special:ApiHelp/main#main/templatedparams|پارامتر الگو]] بر پایهٔ {{PLURAL:$1|مقدار|مقدار}} $2 پیشنهاد میشود.",
"apisandbox-deprecated-parameters": "پارامتر های نامناسب",
"apisandbox-fetch-token": "پرکردن خودکار توکن",
"apisandbox-add-multi": "افزودن",
"speciallogtitlelabel": "هدف (عنوان یا {{ns:user}}:نام کاربر برای کاربر):",
"log": "سیاههها",
"logeventslist-submit": "نمایش",
+ "logeventslist-more-filters": "نمایش سیاهههای بیشتر:",
"logeventslist-patrol-log": "سیاههٔ گشت",
"logeventslist-tag-log": "سیاهه برچسب",
"all-logs-page": "تمام سیاهههای عمومی",
"cachedspecial-refresh-now": "مشاهده آخرین.",
"categories": "ردهها",
"categories-submit": "نمایش",
- "categoriespagetext": "{{PLURAL:$1|ردهٔ|ردههای}} زیر دارای صفحات یا پروندههایی {{PLURAL:$1|است|هستند}}.\n[[Special:UnusedCategories|ردههای استفادهنشده]] در اینجا نمایش داده نشدهاند.\nهمچنین [[Special:WantedCategories|ردههای مورد نیاز]] را ببینید.",
+ "categoriespagetext": "{{PLURAL:$1|ردهٔ|ردههای}} زیر دارای صفحات یا پروندههایی است.\nردههای استفادهنشده در اینجا نمایش داده نشدهاند.\nهمچنین [[Special:WantedCategories|ردههای مورد نیاز]] را ببینید.",
"categoriesfrom": "نمایش ردهها با شروع از:",
"deletedcontributions": "مشارکتهای حذفشده",
"deletedcontributions-title": "مشارکتهای حذفشده",
"activeusers-noresult": "کاربری پیدا نشد.",
"activeusers-submit": "نمایش کاربران فعال",
"listgrouprights": "اختیارات گروههای کاربری",
- "listgrouprights-summary": "فهرست زیر شامل گروههای کاربری تعریف شده در این ویکی و اختیارات داده شده به آنها است.\nاطلاعات بیشتر در مورد هر یک از اختیارات را در [[{{MediaWiki:Listgrouprights-helppage}}]] بیابید.",
+ "listgrouprights-summary": "فهرست زیر شامل گروههای کاربری تعریف شده در این ویکی و اختیارات داده شده به آنها است.\nاطلاعات بیشتر در مورد هر کدام از آنها را در [[{{MediaWiki:Listgrouprights-helppage}}|اختیارات گروههای کاربری]] بیابید.",
"listgrouprights-key": "* <span class=\"listgrouprights-granted\">اختیارات دادهشده</span>\n* <span class=\"listgrouprights-revoked\">اختیارات گرفتهشده</span>",
"listgrouprights-group": "گروه",
"listgrouprights-rights": "دسترسیها",
"dellogpage": "سیاههٔ حذف",
"dellogpagetext": "فهرست زیر فهرستی از آخرین حذفهاست.\nهمهٔ زمانهای نشاندادهشده زمان کارساز (وقت گرینویچ) است.",
"deletionlog": "سیاههٔ حذف",
+ "log-name-create": "سیاههٔ ایجاد صفحه",
+ "log-description-create": "در زیر فهرست صفحاتی که اخیراً ایجاد شدهاند قرار دارد.",
+ "logentry-create-create": "$1 صفحهٔ $3 را {{GENDER:$2|ساخته}}",
"reverted": "به نسخهٔ قدیمیتر واگردانده شد",
"deletecomment": "دلیل:",
"deleteotherreason": "دلیل دیگر/اضافی:",
"protect-othertime": "زمانی دیگر:",
"protect-othertime-op": "زمانی دیگر",
"protect-existing-expiry": "زمان انقضای موجود: $2، $3",
- "protect-existing-expiry-infinity": "زÙ\85اÙ\86 اÙ\86Ù\82ضاÛ\8c Ù\85Ù\88جÙ\88د: بÛ\8câ\80\8cÙ\86Ù\87اÛ\8cت",
+ "protect-existing-expiry-infinity": "زÙ\85اÙ\86 اÙ\86Ù\82ضاÛ\8c Ù\85Ù\88جÙ\88د: بÛ\8câ\80\8cپاÛ\8cاÙ\86",
"protect-otherreason": "دلیل دیگر/اضافی:",
"protect-otherreason-op": "دلیل دیگر",
"protect-dropdown": "*دلایل متداول محافظت\n** خرابکاری گسترده\n** هرزنگاری گسترده\n** جنگ ویرایشی غیر سازنده\n** صفحهٔ پر بازدید",
"uctop": "(نسخهٔ کنونی)",
"month": "در این ماه (و پیش از آن):",
"year": "در این سال (و پیش از آن):",
+ "date": "از تاریخ (و زودتر):",
"sp-contributions-newbies": "فقط مشارکتهای تازهکاران نمایش داده شود",
"sp-contributions-newbies-sub": "برای تازهکاران",
"sp-contributions-newbies-title": "مشارکتهای کاربری برای حسابهای تازهکار",
"interlanguage-link-title": "$1–$2",
"interlanguage-link-title-nonlang": "$1 – $2",
"common.css": "/* دستورات این بخش همهٔ کاربران را تحت تاثیر قرار میدهند. */",
+ "common.json": "/*همهٔ JSONهای اینجا برای همهٔ کاربران در همهٔ صفحات بارگذاری میشوند.*/",
"anonymous": "{{PLURAL:$1|کاربر|کاربران}} گمنام {{SITENAME}}",
"siteuser": "$1، کاربر {{SITENAME}}",
"anonuser": "$1 کاربر ناشناس {{SITENAME}}",
"unlinkaccounts-success": "پیوند کاربری بدون پیوند شد.",
"authenticationdatachange-ignored": "به تغيير اطلاعات احراز هويت پرداخته نشد. آیا ممکن است که هيچ مهيا کنندهای برای اين کار تنظيم نشده باشد؟",
"userjsispublic": "لطفاً توجه کنید: زیرصفحههای جاوااسکریپت نباید حاوی اطلاعات محرمانه باشند چون توسط دیگران قابل مشاهده هستند.",
+ "userjsonispublic": "لطفا توجه داشته باشید: صفحات JSON نباید شامل اطلاعاتی که دیگر کاربران نباید ببینند، باشد.",
"usercssispublic": "لطفاً توجه کنید: زیرصفحههای سیاساس نباید حاوی اطلاعات محرمانه باشند چون توسط دیگران قابل مشاهده هستند.",
"restrictionsfield-badip": "نشانی یا بازهٔ آیپی نامعتبر: $1",
"restrictionsfield-label": "بازههای آیپی مجاز:",
"edit-error-long": "خطاها:\n\n$1",
"revid": "نسخهٔ $1",
"pageid": "شناسهٔ صفحهٔ $1",
+ "interfaceadmin-info": "\n$1\n\nدسترسیها برای ویرایش فایلهای CSS/JS/JSON که اخیراً از دسترسی <code>editinterface</code> جدا شدهاند. اگر نمی دانید که چرا این خطا رخ دادهاست [[mw:MediaWiki_1.32/interface-admin]] را مطالعه کنید.",
"rawhtml-notallowed": "برچسبهای <html> را نمیتوان خارج از صفحههای معمولی استفاده کرد.",
"gotointerwiki": "در حال ترک {{SITENAME}}",
"gotointerwiki-invalid": "عنوان مشخص شده نامجاز است.",
"pagedata-text": "این صفحه یک رابط داده به صفحات است. لطفا نام صفحه را در آدرس به شکل زیرصفحه وارد کنید.\n* مذاکره محتوا با استفاده از هدر Accept ممکن است. این به این معنی است که دادهّای صفحه در قالبی که ترجیح دهید باز خواهد شد.",
"pagedata-not-acceptable": "هیچ قالب تطبیقی یافت نشد. انواع MIME پشتیبانی شده: $1",
"pagedata-bad-title": "عنوان نامعتبر: «$1».",
+ "unregistered-user-config": "برای موارد امنیتی صفحات JavaScript، CSS و JSON برای کاربران ثبتنام نکرده دیده نمیشوند.",
"passwordpolicies": "سیاستهای گذرواژه",
+ "passwordpolicies-summary": "این فهرست سیاستهای موثر بر گذرواژهها برای گروههای کاربری تعریف شده در این ویکیست.",
"passwordpolicies-group": "گروه",
- "passwordpolicies-policies": "سیاستها"
+ "passwordpolicies-policies": "سیاستها",
+ "passwordpolicies-policy-minimalpasswordlength": "گذرواژه باید حداقل $1 {{PLURAL:$1|نویسه|نویسه}} طول داشته باشد",
+ "passwordpolicies-policy-minimumpasswordlengthtologin": "گذرواژه باید حداقل $1 {{PLURAL:$1|نویسه|نویسه}} طول داشته باشد تا بتواند به سامانه وارد شود",
+ "passwordpolicies-policy-passwordcannotmatchusername": "گذرواژه نمی تواند مانند نام کاربری باشد",
+ "passwordpolicies-policy-passwordcannotmatchblacklist": "گذرواژه نمیتواند مشابه گذرواژههای فهرست شده در فهرست سیاه باشد",
+ "passwordpolicies-policy-maximalpasswordlength": "گذرواژه باید کمتر از $1 {{PLURAL:$1|نویسه|نویسه}} طول داشته باشد",
+ "passwordpolicies-policy-passwordcannotbepopular": "گذرواژه نمیتواند {{PLURAL:$1|گذرواژه پراستفاده باشد|در فهرست $1 گذرواژههای پراستفاده باشد}}",
+ "easydeflate-invaliddeflate": "محتوی تهیهشده به صورت درست خالی نشدهاست"
}
"filehist-filesize": "Tiedostokoko",
"filehist-comment": "Kommentti",
"imagelinks": "Tiedoston käyttö",
- "linkstoimage": "{{PLURAL:$1|Seuraavalta sivulta|$1 sivulla}} on linkki tähän tiedostoon:",
- "linkstoimage-more": "Enemmän kuin $1 {{PLURAL:$1|sivu|sivua}} linkittää tähän tiedostoon.\nSeuraava lista näyttää {{PLURAL:$1|ensimmäisen linkittävän sivun|$1 ensimmäistä linkittävää sivua}} tähän tiedostoon.\n[[Special:WhatLinksHere/$2|Koko lista]] on saatavilla.",
- "nolinkstoimage": "Tähän tiedostoon ei ole linkkejä miltään sivulta.",
+ "linkstoimage": "{{PLURAL:$1|Seuraava sivu käyttää|Seuraavat $1 sivua käyttävät}} tätä tiedostoa:",
+ "linkstoimage-more": "Enemmän kuin $1 {{PLURAL:$1|sivu linkittää|sivua linkittävät}} tähän tiedostoon.\nSeuraava lista näyttää {{PLURAL:$1|ensimmäisen sivun, joka käyttää|$1 ensimmäistä sivua, jotka käyttävät}} vain tätä tiedostoa.\n[[Special:WhatLinksHere/$2|Koko lista]] on saatavilla.",
+ "nolinkstoimage": "Tätä tiedostoa ei käytetä millään sivulla.",
"morelinkstoimage": "Näytä [[Special:WhatLinksHere/$1|lisää linkkejä]] tähän tiedostoon.",
"linkstoimage-redirect": "$1 (tiedosto-ohjaus) $2",
"duplicatesoffile": "{{PLURAL:$1|Seuraava tiedosto on tämän tiedoston kaksoiskappale|Seuraavat $1 tiedostoa ovat tämän tiedoston kaksoiskappaleita}} ([[Special:FileDuplicateSearch/$2|lisätietoja]]):",
"uctop": "(uusin)",
"month": "Alkaen kuukaudesta (ja aiemmin):",
"year": "Vuosi",
- "date": "Alkaen päivämäärästä (tai sitä aikaisemmasta):",
+ "date": "Alkaen päivämäärästä (ja sitä aiemmat):",
"sp-contributions-newbies": "Näytä uusien tulokkaiden muutokset",
"sp-contributions-newbies-sub": "Uusien käyttäjien muokkaukset",
"sp-contributions-newbies-title": "Uusien käyttäjien muokkaukset",
"confirm-unwatch-top": "Poistetaanko tämä sivu tarkkailulistaltasi?",
"confirm-rollback-button": "OK",
"confirm-rollback-top": "Palauta tämän sivun muokkaukset?",
+ "confirm-mcrundo-title": "Kumoa muutos",
+ "mcrundofailed": "Kumoaminen epäonnistui",
+ "mcrundo-missingparam": "Tarvittavat parametrit puuttuvat pyynnöstä.",
+ "mcrundo-changed": "Sivu on muuttunut siitä lähtien, kun katsoit tätä muokkausta. Arvioi uusi muokkaus.",
"percent": "$1 %",
"quotation-marks": "\"$1\"",
"imgmultipageprev": "← edellinen sivu",
"group-autoconfirmed": "Utilisateurs autoconfirmés",
"group-bot": "Robots",
"group-sysop": "Administrateurs",
- "group-interface-admin": "Administrateurs d'interfaces",
+ "group-interface-admin": "Administrateurs d'interface",
"group-bureaucrat": "Bureaucrates",
"group-suppress": "Limitateurs",
"group-all": "(tous)",
"confirm-unwatch-top": "Supprimer cette page de votre liste de suivi ?",
"confirm-rollback-button": "OK",
"confirm-rollback-top": "Révoquer les modifications de cette page ?",
+ "confirm-mcrundo-title": "Annuler une modification",
+ "mcrundofailed": "L’annulation a échoué",
+ "mcrundo-missingparam": "Paramètres obligatoires absents dans la requête.",
+ "mcrundo-changed": "La page a été modifiée depuis que vous avez affiché le diff. Veuillez revoir la nouvelle modification.",
"semicolon-separator": " ; ",
"colon-separator": " : ",
"percent": "$1 %",
"userlogin-yourname-ph": "Antré zòt non di itilizatò",
"createacct-another-username-ph": "Antré non-an di itilizatò",
"yourpassword": "Mo di pas :",
- "userlogin-yourpassword": "Mo di pas",
+ "userlogin-yourpassword": "Modipas",
"userlogin-yourpassword-ph": "Antré zòt mo di pas",
"createacct-yourpassword-ph": "Antré oun mo di pas",
"yourpasswordagain": "Konfirmé mo di pas :",
- "createacct-yourpasswordagain": "Konfirmé mo di pas",
+ "createacct-yourpasswordagain": "Konfirmen modipas-a",
"createacct-yourpasswordagain-ph": "Antré òkò menm mo di pas",
"userlogin-remembermypassword": "Gardé mo sésyon aktiv",
"userlogin-signwithsecure": "Itilizé roun konnègsyon sékirizé",
"confirm-unwatch-top": "להסיר את הדף הזה מרשימת המעקב שלך?",
"confirm-rollback-button": "אישור",
"confirm-rollback-top": "לשחזר את העריכות בדף זה?",
+ "confirm-mcrundo-title": "ביטול שינוי",
+ "mcrundofailed": "הביטול נכשל",
+ "mcrundo-missingparam": "חסרים פרמטרים נדרשים בבקשה.",
+ "mcrundo-changed": "הדף שונה מאז הצפייה האחרונה שלך בהבדלים בין הגרסאות. נא לבדוק את השינוי החדש.",
"quotation-marks": "\"$1\"",
"imgmultipageprev": "→ לדף הקודם",
"imgmultipagenext": "לדף הבא ←",
"cascadeprotected": "Ova je stranica zaključana za uređivanja jer je uključena u {{PLURAL:$1|sljedeću stranicu|sljedeće stranice}}, koje su zaštićene \"prenosivom zaštitom\":\n$2",
"namespaceprotected": "Ne možete uređivati stranice u imenskom prostoru '''$1'''.",
"customcssprotected": "Ne možete uređivati ovu CSS stranicu zato što ona sadrži osobne postavke drugog suradnika.",
+ "customjsonprotected": "Ne možete uređivati ovu JSON stranicu zato što ona sadrži osobne postavke drugog suradnika.",
"customjsprotected": "Ne možete uređivati ovu JavaScript stranicu zato što ona sadrži osobne postavke drugog suradnika.",
"mycustomcssprotected": "Nemate ovlasti za uređivanje ove CSS stranice.",
+ "mycustomjsonprotected": "Nemate ovlasti za uređivanje ove JSON stranice.",
"mycustomjsprotected": "Nemate ovlasti za uređivanje ove JavaScript stranice.",
"myprivateinfoprotected": "Nemate ovlasti za uređivanje Vaših osobnih informacija.",
"mypreferencesprotected": "Nemate ovlasti za uređivanje Vaših postavki.",
"Mikławš",
"Macofe",
"Matma Rex",
- "Fitoschido"
+ "Fitoschido",
+ "Vlad5250"
]
},
"tog-underline": "Wotkazy podšmórnić:",
"cascadeprotected": "Tuta strona je za wobdźěłowanje zawrjena, dokelž je w {{PLURAL:$1|slědowacej stronje|slědowacymaj stronomaj|slědowacych stronach}} zapřijata, {{PLURAL:$1|kotraž je|kotrejž stej|kotrež su}} přez kaskadowu opciju {{PLURAL:$1|škitana|škitanej|škitane}}:\n$2",
"namespaceprotected": "Nimaš dowolnosć, zo by stronu w mjenowym rumje '''$1''' wobdźěłał.",
"customcssprotected": "Nimaš prawo, zo by tutu CSS-stronu wobdźěłał, dokelž wosobinske nastajenja druheho wužiwarja wobsahuje.",
+ "customjsonprotected": "Nimaš prawo, zo by tutu JSON-stronu wobdźěłał, dokelž wosobinske nastajenja druheho wužiwarja wobsahuje.",
"customjsprotected": "Nimaš prawo, zo by tutu JavaScript-stronu wobdźěłał, dokelž wosobinske nastajenja druheho wužiwarja wobsahuje.",
"mycustomcssprotected": "Nimaš prawo tutu CSS-stronu wobdźěłać.",
+ "mycustomjsonprotected": "Nimaš prawo tutu JSON-stronu wobdźěłać.",
"mycustomjsprotected": "Nimaš prawo tutu JavaScript-stronu wobdźěłać.",
"myprivateinfoprotected": "Nimaš prawo swoje priwatne informacije wobdźěłać.",
"mypreferencesprotected": "Nimaš prawo swoje nastajenja wobdźěłać.",
"recentchanges-label-minor": "Սա չնչին խմբագրում է",
"recentchanges-label-bot": "Այս խմբագրումը կատարվել է բոտի կողմից",
"recentchanges-label-unpatrolled": "Այս խմբագրումը դեռ չի պարեկվել",
- "recentchanges-label-plusminus": "Էջի չափսը փոփոխվեց այսքան բայթով",
+ "recentchanges-label-plusminus": "Էջի չափսը փոփոխվել է այսքան բայթով",
"recentchanges-legend-heading": "<strong>Լեգենդ՝</strong>",
"recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (տես նաև՝ [[Special:NewPages|նոր էջերի ցանկ]])",
"recentchanges-submit": "Ցույց տալ",
"rcfilters-view-tags": "Պիտակված խմբագրումներ",
"rcfilters-liveupdates-button": "Կենդանի թարմացումներ",
"rcnotefrom": "Ստորև բերված են փոփոխությունները սկսած՝ '''$2''' (մինչև՝ '''$1''')։",
- "rclistfrom": "Ցույց տալ նոր փոփոխությունները սկսած $3 $2",
+ "rclistfrom": "Ցույց տալ նոր փոփոխությունները՝ սկսած $3 $2",
"rcshowhideminor": "$1 չնչին խմբագրումները",
"rcshowhideminor-show": "Ցուցադրել",
"rcshowhideminor-hide": "Թաքցնել",
"confirm-unwatch-top": "Remover iste pagina de tu observatorio?",
"confirm-rollback-button": "OK",
"confirm-rollback-top": "Reverter le modificationes a iste pagina?",
+ "confirm-mcrundo-title": "Disfacer un modification",
+ "mcrundofailed": "Disfaction fallite",
+ "mcrundo-missingparam": "Manca parametros obligatori in le requesta.",
+ "mcrundo-changed": "Le pagina ha essite modificate post que tu examinava le differentias. Per favor revide le nove modification.",
"quotation-marks": "“$1”",
"imgmultipageprev": "← precedente pagina",
"imgmultipagenext": "sequente pagina →",
"confirm-unwatch-top": "Rimuovere questa pagina dalla tua lista degli osservati speciali?",
"confirm-rollback-button": "OK",
"confirm-rollback-top": "Ripristinare le modifiche di questa pagina?",
+ "confirm-mcrundo-title": "Annulla una modifica",
+ "mcrundofailed": "Annullamento fallito",
+ "mcrundo-missingparam": "Parametri obbligatori mancanti nella richiesta.",
"percent": "$1 %",
"quotation-marks": "«$1»",
"imgmultipageprev": "← pagina precedente",
"botpasswords-restriction-failed": "ボットパスワード制限によりログインできません。",
"botpasswords-invalid-name": "指定された利用者名には、ボット用パスワードの区切りである「$1」 が含まれていません。",
"botpasswords-not-exist": "利用者「$1」はボット「$2」のパスワードを所持していません。",
+ "botpasswords-needs-reset": "{{GENDER:$1|利用者}}「$1」のボット名「$2」のためのパスワードはリセットする必要があります。",
"resetpass_forbidden": "パスワードは変更できません",
"resetpass_forbidden-reason": "パスワードは変更できません: $1",
"resetpass-no-info": "このページに直接アクセスするためにはログインしている必要があります。",
"updated": "(更新)",
"note": "<strong>お知らせ:</strong>",
"previewnote": "<strong>これはプレビューです。</strong>\n変更内容はまだ保存されていません!",
- "continue-editing": "ç·¨é\9b\86ã\82\92ç¶\9aè¡\8c",
+ "continue-editing": "ç·¨é\9b\86ã\82¨ã\83ªã\82¢ã\81«ç§»å\8b\95ã\81\99ã\82\8b",
"previewconflict": "これは、上の編集エリアの文章を保存した場合にどう表示されるかを示すプレビューです。",
"session_fail_preview": "申し訳ありません! セッションデータが消失したため編集を処理できませんでした。\n\nアカウントがログアウトされている可能性があります。<strong>アカウントにログインしていることを確認して、もう一度やり直してください</strong>。\nそれでも失敗する場合、[[Special:UserLogout|ログアウト]]してからログインし直し、現在使用しているブラウザでこのサイトからのクッキーが許可されていることを確認してください。",
"session_fail_preview_html": "申し訳ありません! セッション データが消失したため編集を処理できませんでした。\n\n<em>{{SITENAME}}では生のHTMLが有効であり、JavaScriptでの攻撃を予防するためにプレビューを表示していません。</em>\n\n<strong>この編集が問題ない場合はもう一度保存してください。</strong>\nそれでも失敗する場合、[[Special:UserLogout|ログアウト]]してからログインし直し、現在使用しているブラウザでこのサイトからのクッキーが許可されていることを確認してください。",
"grouppage-autoconfirmed": "{{ns:project}}:自動承認された利用者",
"grouppage-bot": "{{ns:project}}:ボット",
"grouppage-sysop": "{{ns:project}}:管理者",
+ "grouppage-interface-admin": "{{ns:project}}:インターフェース管理者",
"grouppage-bureaucrat": "{{ns:project}}:ビューロクラット",
"grouppage-suppress": "{{ns:project}}:秘匿者",
"right-read": "ページを閲覧",
"rcfilters-preference-help": "2017年のインターフェース更新およびそれ以降に追加された新しいツールを無効化します。",
"rcfilters-watchlist-preference-label": "ウォッチリストの改善版を隠す",
"rcfilters-filter-showlinkedfrom-label": "指定されたページのリンク先の変更を表示",
- "rcfilters-filter-showlinkedfrom-option-label": "指定されたページから<strong>リンクされているページ</strong>",
+ "rcfilters-filter-showlinkedfrom-option-label": "指定されたページから<strong>リンクされているページ(リンク先)</strong>",
"rcfilters-filter-showlinkedto-label": "指定されたページのリンク元の変更を表示",
- "rcfilters-filter-showlinkedto-option-label": "指定されたページに<strong>リンクしているページ</strong>",
+ "rcfilters-filter-showlinkedto-option-label": "指定されたページに<strong>リンクしているページ(リンク元)</strong>",
"rcfilters-target-page-placeholder": "ページ名(またはカテゴリ名)を入力",
"rcnotefrom": "以下は<strong>$3 $4</strong>以降の{{PLURAL:$5|更新です}} (最大 <strong>$1</strong> 件)。",
"rclistfromreset": "日時指定をリセット",
"uploadstash-zero-length": "ファイルのサイズがゼロです。",
"invalid-chunk-offset": "無効なチャンクオフセット",
"img-auth-accessdenied": "アクセスが拒否されました",
- "img-auth-nopathinfo": "PATH_INFO が見つかりません。\nサーバーが、この情報を渡すように構成されていません。\nCGI ベースであるため、img_auth に対応できない可能性もあります。\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization をご覧ください。",
+ "img-auth-nopathinfo": "URL のパス情報が見つかりません。\nサーバーは、変数 REQUEST_URI または PATH_INFO の一方または両方でパス情報を渡すように構成する必要があります。\nすでに設定済みの場合は、$wgUsePathInfo を有効にすることをお試しください。\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization をご覧ください。",
"img-auth-notindir": "要求されたパスは、設定済みのアップロード先ディレクトリ内にありません。",
"img-auth-badtitle": "「$1」からは有効なページ名を構築できません。",
"img-auth-nologinnWL": "ログインしておらず、さらに「$1」はホワイトリストに入っていません。",
"cachedspecial-refresh-now": "最新版を表示します。",
"categories": "カテゴリ",
"categories-submit": "表示",
- "categoriespagetext": "以ä¸\8bã\81®{{PLURAL:$1|ã\82«ã\83\86ã\82´ã\83ª}}ã\81«ã\81¯ã\83\9aã\83¼ã\82¸ã\81¾ã\81\9fã\81¯ã\83¡ã\83\87ã\82£ã\82¢ã\81\8cã\81\82ã\82\8aã\81¾ã\81\99ã\80\82\n[[Special:UnusedCategories|使ã\82\8fã\82\8cã\81¦ã\81\84ã\81ªã\81\84ã\82«ã\83\86ã\82´ã\83ª]]ã\81¯ã\81\93ã\81\93ã\81«ã\81¯è¡¨ç¤ºã\81\97ã\81¦ã\81\84ã\81¾ã\81\9bã\82\93。\n[[Special:WantedCategories|カテゴリページが存在しないカテゴリ]]も参照してください。",
+ "categoriespagetext": "以ä¸\8bã\81®{{PLURAL:$1|ã\82«ã\83\86ã\82´ã\83ª}}ã\81¯ã\82¦ã\82£ã\82ä¸\8aã\81«ã\81\82ã\82\8aã\80\81æ\9cªä½¿ç\94¨ã\81§ã\81\82ã\82\8bå ´å\90\88ã\82\82ã\81\82ã\82\8aã\81¾ã\81\99。\n[[Special:WantedCategories|カテゴリページが存在しないカテゴリ]]も参照してください。",
"categoriesfrom": "最初に表示するカテゴリ:",
"deletedcontributions": "利用者の削除された投稿",
"deletedcontributions-title": "利用者の削除された投稿",
"Yogesh",
"Lokesha kunchadka",
"Anoop rao",
- "Rakshika"
+ "Rakshika",
+ "Gopala Krishna A"
]
},
"tog-underline": "ಕೊಂಡಿಗಳ ಕೆಳಗೆ ಗೆರೆ ತೋರಿಸಿ",
"botpasswords-existing": "ಆಸ್ಥಿತ್ವದಲ್ಲಿರುವ ಬಾಟ್ ಪ್ರವೇಶಪದ",
"botpasswords-createnew": "ಹೊಸ ಬಾಟ್ ಪ್ರವೇಶಪದ ರಚಿಸಿ",
"botpasswords-editexisting": "ಆಸ್ಥಿತ್ವದಲ್ಲಿರುವ ಬಾಟ್ ಪ್ರವೇಶಪದ ಸ೦ಪಾದಿಸಿ",
+ "botpasswords-label-create": "ಸೃಷ್ಟಿಸು",
"resetpass_forbidden": "ಪ್ರವೇಶಪದಗಳನ್ನು ಬದಲಾಯಿಸುವಂತಿಲ್ಲ.",
"resetpass-no-info": "ನೀವು ಈ ಪುಟವನ್ನು ನೇರತಲುಪಲು ಲಾಗಿನ್ ಆಗಿರುವುದು ಆವಶ್ಯಕ.",
"resetpass-submit-loggedin": "ಪ್ರವೇಶಪದ ಬದಲಾಯಿಸು",
"cannotcreateaccount-text": "이 위키에서 직접 계정 만들기는 활성화되어 있지 않습니다.",
"yourdomainname": "도메인 이름:",
"password-change-forbidden": "이 위키에서 비밀번호를 바꿀 수 없습니다.",
- "externaldberror": "인증 데이터베이스에 오류가 있거나 바깥 계정을 새로 고칠 권한이 없습니다.",
+ "externaldberror": "인증 데이터베이스에 오류가 있거나 외부 계정을 새로 고칠 권한이 없습니다.",
"login": "로그인",
"login-security": "사용자 정보 확인",
"nav-login-createaccount": "로그인 / 계정 만들기",
"powersearch-toggleall": "모두",
"powersearch-togglenone": "모두 제외",
"powersearch-remember": "향후 검색에 선택 기억하기",
- "search-external": "바깥 검색",
+ "search-external": "외부 검색",
"searchdisabled": "{{SITENAME}} 검색이 비활성화되어 있습니다.\n검색이 작동하지 않는 동안 Google을 통해 검색할 수 있습니다.\n검색 엔진의 내용은 최신이 아닐 수 있다는 점을 참고하세요.",
"search-error": "검색하는 동안 오류가 발생했습니다: $1",
"search-warning": "검색하는 동안 경고가 발생했습니다: $1",
"img-auth-nofile": "\"$1\" 파일이 없습니다.",
"img-auth-isdir": "\"$1\" 디렉터리에 접근을 시도했습니다.\n파일에만 접근할 수 있습니다.",
"img-auth-streaming": "\"$1\" 파일을 전송하는 중입니다.",
- "img-auth-public": "img_auth.php는 개인 위키 파일을 바깥 사이트로 전송하는 기능입니다.\n이 기능은 기본적으로 공개적인 위키에서 사용하도록 설계되어 있습니다.\n보안적인 문제로 기본적으로 img_auth.php 기능은 비활성화되어 있습니다.",
+ "img-auth-public": "img_auth.php의 기능은 개인 위키의 파일을 외부로 전송하는 기능입니다.\n이 위키는 공개된 위키로 구성되어 있습니다.\n최적의 보안을 위해 img_auth.php는 비활성화되어 있습니다.",
"img-auth-noread": "\"$1\" 파일을 볼 권한이 없습니다.",
"http-invalid-url": "잘못된 URL: $1",
"http-invalid-scheme": "\"$1\"(으)로 시작하는 URL은 지원되지 않습니다.",
"wantedpages-summary": "다른 문서들에 링크는 걸려 있지만 존재하지 않는 문서들 중, 넘겨주기 문서를 제외한 목록입니다. 존재하지 않는 문서로 넘겨주는 문서 목록을 보려면 [[{{#special:BrokenRedirects}}|끊긴 넘겨주기 목록]]을 참조하세요.",
"wantedpages-badtitle": "문서 제목이 잘못되었습니다: $1",
"wantedfiles": "필요한 파일 목록",
- "wantedfiletext-cat": "다음 파일은 쓰이고는 있지만 없는 파일입니다. 바깥 저장소에 있는 파일은 실제로는 있지만 여기 올라 있을 수 있습니다. 그런 오류는 <del>삭제선</del>이 그어질 것입니다. 또한 없는 파일을 포함하고 있는 문서는 [[:$1]]에 올라 있습니다.",
+ "wantedfiletext-cat": "다음 파일은 쓰이고는 있지만 없는 파일입니다. 외부 저장소의 파일은 존재하더라도 여기에 나열될 수 있습니다. 이러한 오류는 <del>삭제선</del>이 그어질 것입니다. 또한 없는 파일을 포함하고 있는 문서는 [[:$1]]에 나열됩니다.",
"wantedfiletext-cat-noforeign": "다음 파일은 쓰이고 있지만 존재하지 않습니다. 또한, 존재하지 않는 파일이 포함된 문서가 [[:$1]]에 나열되어 있습니다.",
- "wantedfiletext-nocat": "다음 파일은 쓰이고 있지만 존재하지 않습니다. 바깥 저장소에 있는 파일은 실제로는 있지만 여기 올라 있을 수 있습니다. 그런 오류는 <del>삭제선</del>이 그어질 것입니다.",
+ "wantedfiletext-nocat": "다음 파일은 쓰이고는 있지만 없는 파일입니다. 외부 저장소의 파일은 존재하더라도 여기에 나열될 수 있습니다. 이러한 오류는 <del>삭제선</del>이 그어질 것입니다.",
"wantedfiletext-nocat-noforeign": "다음 파일은 쓰이고 있지만 존재하지 않습니다.",
"wantedtemplates": "필요한 틀 목록",
"mostlinked": "가장 많이 연결된 문서 목록",
"booksources-search-legend": "책 원본 검색",
"booksources-isbn": "ISBN:",
"booksources-search": "검색",
- "booksources-text": "ì\95\84ë\9e\98ì\9d\98 목ë¡\9dì\9d\80 ì\83\88 ì±\85ì\9d´ë\82\98 ì¤\91ê³ ì±\85ì\9d\84 í\8c\90매í\95\98ë\8a\94 ë°\94ê¹¥ ì\82¬ì\9d´í\8a¸ë¡\9c, ì\9b\90í\95\98ë\8a\94 ì±\85ì\9d\98 ì \95보를 ì\96»ì\9d\84 ì\88\98 ì\9e\88ì\8aµë\8b\88ë\8b¤.",
+ "booksources-text": "ì\95\84ë\9e\98ì\97\90 ì\83\88 ì±\85ì\9d´ë\82\98 ì¤\91ê³ ì±\85ì\9d\84 í\8c\90매í\95\98ë\8a\94 ë\8b¤ë¥¸ ì\82¬ì\9d´í\8a¸ì\9d\98 ë§\81í\81¬ 목ë¡\9dì\9d´ ì\9e\88ì\9c¼ë©°, ì\9b\90í\95\98ë\8a\94 ì±\85ì\9d\98 ì¶\94ê°\80 ì \95ë³´ë\8f\84 í\99\95ì\9d¸í\95 ì\88\98 ì\9e\88ì\8aµë\8b\88ë\8b¤:",
"booksources-invalid-isbn": "입력한 ISBN이 올바르지 않은 것으로 보입니다. 원본과 대조해 문제가 있는지 확인해보세요.",
"magiclink-tracking-rfc": "RFC 매직 링크를 사용하는 문서",
"magiclink-tracking-rfc-desc": "이 문서는 RFC 매직 링크를 사용합니다. 이관 방법을 보려면 [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org]를 참조하십시오.",
"ipaddressorusername": "IP 주소 또는 사용자 이름:",
"ipbexpiry": "기한:",
"ipbreason": "이유:",
- "ipbreason-dropdown": "*일반적인 차단 이유\n** 거짓 정보를 넣음\n** 문서 내용을 지움\n** 바깥 사이트의 광고성 링크를 넣음\n** 문서에 장난성 내용을 넣음\n** 협박성 행동\n** 다중 계정 악용\n** 부적절한 사용자 이름",
+ "ipbreason-dropdown": "*일반적인 차단 이유\n** 거짓 정보를 넣음\n** 문서 내용을 지움\n** 외부 사이트의 광고성 링크를 넣음\n** 문서에 장난성 내용을 넣음\n** 협박성 행동\n** 다중 계정 악용\n** 부적절한 사용자 이름",
"ipb-hardblock": "이 IP를 이용하는 로그인한 사용자가 편집하는 것을 막기",
"ipbcreateaccount": "계정 만들기를 막기",
"ipbemailban": "이메일을 보내지 못하도록 막기",
"confirm-unwatch-top": "이 문서를 주시문서 목록에서 뺄까요?",
"confirm-rollback-button": "확인",
"confirm-rollback-top": "이 문서의 편집을 되돌리시겠습니까?",
+ "confirm-mcrundo-title": "변경사항 취소",
+ "mcrundofailed": "실행 취소를 실패했습니다",
+ "mcrundo-missingparam": "요청에 필요한 변수가 존재하지 않습니다.",
+ "mcrundo-changed": "차이를 본 이후로 문서가 변경되었습니다. 새로운 변경사항을 검토해 주십시오.",
"quotation-marks": "“$1”",
"imgmultipageprev": "← 이전 페이지",
"imgmultipagenext": "다음 페이지 →",
"specialpages-group-developer": "개발자 도구",
"blankpage": "빈 문서",
"intentionallyblankpage": "일부러 비워 둔 문서입니다.",
- "external_image_whitelist": " #이 줄은 그대로 두십시오<pre>\n#정규 표현식(// 사이에 있는 부분)을 아래에 입력하세요.\n#이 목록은 바깥 그림의 URL과 대조할 것입니다.\n#이 목록과 일치하는 것은 그림으로 표시되지만, 그렇지 않은 경우 그림을 가리키는 링크만 보이게 될 것입니다.\n#\"#\" 문자에서 줄의 끝까지는 주석입니다\n#이 목록은 대소문자를 구별하지 않습니다\n\n#모든 정규 표현식은 이 줄 위에 넣어 주십시오. 그리고 이 줄은 그대로 두십시오.</pre>",
+ "external_image_whitelist": " #이 줄은 그대로 두십시오<pre>\n#정규 표현식(// 사이에 있는 부분)을 아래에 입력하세요.\n#이 목록은 외부 그림의 URL과 대조할 것입니다.\n#이 목록과 일치하는 것은 그림으로 표시되지만, 그렇지 않은 경우 그림을 가리키는 링크만 보이게 될 것입니다.\n#\"#\" 문자에서 줄의 끝까지는 주석입니다\n#이 목록은 대소문자를 구별하지 않습니다\n\n#모든 정규 표현식은 이 줄 위에 넣어 주십시오. 그리고 이 줄은 그대로 두십시오.</pre>",
"tags": "올바른 편집 태그",
"tag-filter": "[[Special:Tags|태그]] 필터:",
"tag-filter-submit": "필터",
"passwordpolicies-policy-passwordcannotmatchusername": "비밀번호는 사용자 이름과 같을 수 없습니다",
"passwordpolicies-policy-passwordcannotmatchblacklist": "비밀번호는 블랙리스트에 있는 비밀번호와 일치할 수 없습니다",
"passwordpolicies-policy-maximalpasswordlength": "비밀번호는 적어도 $1 {{PLURAL:$1|자}} 미만이어야 합니다",
- "passwordpolicies-policy-passwordcannotbepopular": "비밀번호는 {{PLURAL:$1|저명한 비밀번호가 될|$1개의 저명한 비밀번호에 속할}} 수 없습니다"
+ "passwordpolicies-policy-passwordcannotbepopular": "비밀번호는 {{PLURAL:$1|저명한 비밀번호가 될|$1개의 저명한 비밀번호에 속할}} 수 없습니다",
+ "easydeflate-invaliddeflate": "주어진 컨텐츠가 적절히 압축되지 않았습니다"
}
"confirm-unwatch-top": "Dës Säit vun Ärer Iwwerwaachungslëscht erofhuelen?",
"confirm-rollback-button": "OK",
"confirm-rollback-top": "Ännerunge vun dëser Säit zrécksetzen?",
+ "confirm-mcrundo-title": "Eng Ännerung réckgängeg maachen",
"quotation-marks": "\"$1\"",
"imgmultipageprev": "← Vireg Säit",
"imgmultipagenext": "nächst Säit →",
"resetpass-submit-loggedin": "Keisti slaptažodį",
"resetpass-submit-cancel": "Atšaukti",
"resetpass-wrong-oldpass": "Klaidingas laikinas ar esamas slaptažodis.\nJūs galbūt jau sėkmingai pakeitėte savo slaptažodį ar jau prašėte naujo laikino slaptažodžio.",
- "resetpass-recycled": "Atkurkite savo slaptažodį kitokiu, nei buvo prieš tai.",
+ "resetpass-recycled": "Pakeiskite savo slaptažodį kitokiu, nei buvo prieš tai.",
"resetpass-temp-emailed": "Jūs prisijungęs laikinu slaptažodžiu, gautu per elektroninį paštą. Kad baigtumėte jungtis, čia turite nustatyti naują slaptažodį:",
"resetpass-temp-password": "Laikinas slaptažodis:",
"resetpass-abort-generic": "Slaptažodžio keitimas buvo nutrauktas nuo ekstenzijos.",
"resetpass-expired": "Jūsų slaptažodžio galiojimas baigėsi. Prašome nustatyti naują prisijungimo slaptažodį.",
- "resetpass-expired-soft": "Jūsų slaptažodžio galiojimas baigėsi ir jį reikia atkurti iš naujo. Pasirinkite naują slaptažodį dabar arba spauskite \"{{int:authprovider-resetpass-skip-label}}\", kad būtų atstatytas vėliau.",
- "resetpass-validity-soft": "Jūsų slaptažodis netinkamas: $1\n\nPasirinkite naują slaptažodį dabar arba spauskite \"{{int:authprovider-resetpass-skip-label}}\", kad būtų atkurtas vėliau.",
+ "resetpass-expired-soft": "Jūsų slaptažodžio galiojimas baigėsi ir jį reikia pakeisti. Pasirinkite naują slaptažodį dabar arba spauskite \"{{int:authprovider-resetpass-skip-label}}\", kad būtų pakeistas vėliau.",
+ "resetpass-validity-soft": "Jūsų slaptažodis netinkamas: $1\n\nPasirinkite naują slaptažodį dabar arba spauskite \"{{int:authprovider-resetpass-skip-label}}\", kad būtų pakeistas vėliau.",
"passwordreset": "Atkurti slaptažodį",
"passwordreset-text-one": "Užpildykite šią formą, norėdami atkurti savo slaptažodį.",
"passwordreset-text-many": "{{PLURAL:$1|Užpildykite vieną iš laukelių, kad el. paštu gautumėte laikinąjį slaptažodį.}}",
"filehist-filesize": "Rinkmenos dydis",
"filehist-comment": "Paaiškinimas",
"imagelinks": "Rinkmenos naudojimas",
- "linkstoimage": "{{PLURAL:$1|Šis puslapis|Šie puslapiai}} nurodo į šią rinkmeną:",
- "linkstoimage-more": "Daugiau nei $1 {{PLURAL:$1|puslapis|puslapiai|puslapių}} rodo į šį failą.\nŠis sąrašas rodo tik {{PLURAL:$1|puslapio|pirmų $1 puslapių}} nuorodas į šį failą.\nYra pasiekiamas ir [[Special:WhatLinksHere/$2|visas sąrašas]].",
- "nolinkstoimage": "Į rinkmeną nenurodo joks puslapis.",
+ "linkstoimage": "{{PLURAL:$1|Šis puslapis|Šie puslapiai}} naudoja šią rinkmeną:",
+ "linkstoimage-more": "Daugiau nei $1 {{PLURAL:$1|puslapis|puslapiai|puslapių}} naudoja šią rinkmeną.\nŠis sąrašas rodo tik {{PLURAL:$1|puslapį, naudojantį|pirmus $1 puslapius, naudojančius|pirmus $1 puslapių, naudojančių}} šį failą.\nYra pasiekiamas ir [[Special:WhatLinksHere/$2|visas sąrašas]].",
+ "nolinkstoimage": "Rinkmena nėra naudojama jokiame puslapyje.",
"morelinkstoimage": "Žiūrėti [[Special:WhatLinksHere/$1|daugiau nuorodų]] į šį failą.",
"linkstoimage-redirect": "$1 (failo peradresavimas) $2",
"duplicatesoffile": "Šis failas turi {{PLURAL:$1|$1 dublikatą|$1 dublikatus|$1 dublikatų}} ([[Special:FileDuplicateSearch/$2|daugiau informacijos]]):",
"Zuiks",
"Martinsdzerve",
"Nixiéoffset",
- "Fitoschido"
+ "Fitoschido",
+ "Mailman"
]
},
"tog-underline": "Pasvītrot saites:",
"title-invalid-characters": "Pieprasītais lapas nosaukums satur nederīgus simbolus: \"$1\".",
"title-invalid-magic-tilde": "Pieprasītās lapas nosaukums satur nederīgu maģiskās tildes virkni (<nowiki>~~~</nowiki>).",
"title-invalid-leading-colon": "Pieprasītās lapas nosaukums satur neatļautu kolu tā sākumā.",
- "perfcached": "Šie dati ir no servera kešatmiņas un var būt novecojuši. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.",
+ "perfcached": "Šie dati ir no servera kešatmiņas un var būt novecojuši. Kešatmiņā ir {{PLURAL:$1|pieejami|pieejams|pieejami}} ne vairāk kā {{PLURAL:$1|$1 rezultāti|viens rezultāts|$1 rezultāti}}.",
"perfcachedts": "Šie dati ir no servera kešatmiņas (''cache''), kas pēdējo reizi bija atjaunota $1. Kešatmiņā {{PLURAL:$4|pieejami|pieejams|pieejami}} ne vairāk kā {{PLURAL:$4|$4 rezultāti|viens rezultāts|$4 rezultāti}}.",
"querypage-no-updates": "Šīs lapas atjaunošana pagaidām ir atslēgta. Te esošie dati tuvākajā laikā netiks atjaunoti.",
"viewsource": "Aplūkot kodu",
"cannotchangeemail": "Konta e-pasta adresi nevar nomainīt šajā wiki.",
"emaildisabled": "Šī vietne nevar nosūtīt e-pastus.",
"accountcreated": "Konts izveidots",
- "accountcreatedtext": "Lietotāja konts priekš $1 tika izveidots.",
+ "accountcreatedtext": "Lietotāja konts priekš [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|diskusija]]) tika izveidots.",
"createaccount-title": "Dalībnieka konta izveidošana {{grammar:lokatīvs|{{SITENAME}}}}",
"login-throttled": "Jūs esat veicis pārāk daudz pieslēgšanās mēģinājumus.\nLūdzu, uzgaidiet $1 pirms mēģiniet vēlreiz.",
"login-abort-generic": "Pieteikšanās neizdevās — darbība pārtraukta",
"yourtext": "Tavs teksts",
"storedversion": "Saglabātā versija",
"editingold": "'''BRĪDINĀJUMS: Saglabājot šo lapu, tu izmainīsi šīs lapas novecojušu versiju, un ar to tiks dzēstas visas izmaiņas, kas izdarītas pēc šīs versijas.'''",
+ "unicode-support-fail": "Izskatās, ka tava pārlūkprogramma neatbalsta Unicode. Labojums netika saglabāts.",
"yourdiff": "Atšķirības",
"copyrightwarning": "Lūdzu, ņem vērā, ka viss ieguldījums, kas veikts {{grammar:lokatīvs|{{SITENAME}}}}, ir uzskatāms par publiskotu saskaņā ar $2 (vairāk info skatīt $1).\nJa nevēlies, lai Tevis rakstīto kāds labo un izplata tālāk, tad, lūdzu, nepievieno to šeit!<br />\n\nIzvēloties \"Saglabāt lapu\", Tu apliecini, ka šo rakstu esi rakstījis vai papildinājis pats vai izmantojis informāciju no darba, ko neaizsargā autortiesības, vai tamlīdzīga brīvi pieejama resursa.\n'''BEZ ATĻAUJAS NEPIEVIENO DARBU, KO AIZSARGĀ AUTORTIESĪBAS!'''",
"copyrightwarning2": "Lūdz ņem vērā, ka visu ieguldījumu {{grammar:lokatīvs|{{SITENAME}}}} var rediģēt, mainīt vai izdzēst citi lietotāji. Ja negribi lai ar tavu rakstīto tā izrīkojas, nepievieno to šeit.\n\nTu apliecini, ka šo rakstu esi rakstījis vai papildinājis pats vai izmantojis informāciju no darba, ko neaizsargā autortiesības, vai tamlīdzīga brīvi pieejama resursa (sīkāk skatīt $1).\n\n'''BEZ ATĻAUJAS NEPIEVIENO DARBU, KO AIZSARGĀ AUTORTIESĪBAS!'''",
"page_last": "pēdējā",
"histlegend": "Atšķirību izvēle: atzīmē vajadzīgo versiju apaļās pogas un spied \"Salīdzināt izvēlētās versijas\".<br />\nApzīmējumi:\n\"ar pašreizējo\" = salīdzināt ar pašreizējo versiju,\n\"ar iepriekšējo\" = salīdzināt ar iepriekšējo versiju,\nm = maznozīmīgs labojums.",
"history-fieldset-title": "Versiju meklēšana",
- "history-show-deleted": "Tikai dzēstās",
+ "history-show-deleted": "Tikai dzēstie labojumi",
"histfirst": "Senākās",
"histlast": "Jaunākās",
"historysize": "({{PLURAL:$1|$1 baiti|1 baits|$1 baiti}})",
"group-autoconfirmed": "Automātiski apstiprinātie dalībnieki",
"group-bot": "Boti",
"group-sysop": "Administratori",
+ "group-interface-admin": "Interfeisa administrators",
"group-bureaucrat": "Birokrāti",
"group-suppress": "Cenzētāji",
"group-all": "(visi)",
"grouppage-autoconfirmed": "{{ns:project}}:Automātiski apstiprināti dalībnieki",
"grouppage-bot": "{{ns:project}}:Boti",
"grouppage-sysop": "{{ns:project}}:Administratori",
+ "grouppage-interface-admin": "{{ns:project}}:Interfeisa administratori",
"grouppage-bureaucrat": "{{ns:project}}:Birokrāti",
"grouppage-suppress": "{{ns:project}}:Cenzētāji",
"right-read": "Lasīt lapas",
"right-deletedtext": "Apskatīt izdzēsto tekstu un izmaiņas starp izdzēstām versijām",
"right-browsearchive": "Meklēt izdzēstās lapas",
"right-undelete": "Atjaunot lapu",
- "right-suppressrevision": "Apskatīt un atjaunot versijas, kas paslēptas no adminiem",
+ "right-suppressrevision": "Apskatīt un atjaunot visas lapas versijas",
"right-suppressionlog": "Skatīt personīgos reģistrus",
"right-block": "Bloķēt citus dalībniekus (lapu izmainīšana)",
"right-blockemail": "Bloķēt citus dalībniekus (iespēja sūtīt e-pastu)",
"filehist-filesize": "Големина",
"filehist-comment": "Коментар",
"imagelinks": "Употреба на податотеката",
- "linkstoimage": "Ð\94о оваа подаÑ\82оÑ\82ека {{PLURAL:$1|води Ñ\81леднава Ñ\81Ñ\82Ñ\80аниÑ\86а|водаÑ\82 следниве $1 страници}}:",
- "linkstoimage-more": "Ð\9fовеÑ\9cе од {{PLURAL:$1|една Ñ\81Ñ\82Ñ\80аниÑ\86а е повÑ\80зана|$1 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\81е повÑ\80зани}} Ñ\81о оваа подаÑ\82оÑ\82ека.\nСледниов Ñ\81пиÑ\81ок {{PLURAL:$1|Ñ\98а пÑ\80икажÑ\83ва Ñ\81амо пÑ\80ваÑ\82а повÑ\80зана Ñ\81Ñ\82Ñ\80аниÑ\86а|ги пÑ\80икажÑ\83ва Ñ\81амо пÑ\80виÑ\82е $1 повÑ\80зани Ñ\81Ñ\82Ñ\80аниÑ\86и}} до оваа подаÑ\82оÑ\82ека.\nЦелоÑ\81ен Ñ\81пиÑ\81ок може да добиете [[Special:WhatLinksHere/$2|тука]].",
+ "linkstoimage": "Ð\9fодаÑ\82оÑ\82екава Ñ\81е коÑ\80иÑ\81Ñ\82и во {{PLURAL:$1|Ñ\81леднава Ñ\81Ñ\82Ñ\80аниÑ\86а|следниве $1 страници}}:",
+ "linkstoimage-more": "Ð\9fодаÑ\82оÑ\82екава Ñ\81е коÑ\80иÑ\81Ñ\82и во повеÑ\9cе од {{PLURAL:$1|една Ñ\81Ñ\82Ñ\80аниÑ\86а|$1 Ñ\81Ñ\82Ñ\80аниÑ\86и}}.\nСледниов Ñ\81пиÑ\81ок {{PLURAL:$1|Ñ\98а пÑ\80икажÑ\83ва Ñ\81амо пÑ\80ваÑ\82а Ñ\81Ñ\82Ñ\80аниÑ\86а|ги пÑ\80икажÑ\83ва Ñ\81амо пÑ\80виÑ\82е $1 Ñ\81Ñ\82Ñ\80аниÑ\86и}} Ñ\88Ñ\82о Ñ\98а коÑ\80иÑ\81Ñ\82аÑ\82 подаÑ\82оÑ\82екаÑ\82а.\nЦелоÑ\81ен Ñ\81пиÑ\81ок Ñ\9cе наÑ\98дете [[Special:WhatLinksHere/$2|тука]].",
"nolinkstoimage": "Нема страници што ја користат оваа податотека.",
"morelinkstoimage": "Погледајте ги [[Special:WhatLinksHere/$1|останатите врски]] кон оваа податотека.",
"linkstoimage-redirect": "$1 (пренасочување) $2",
"confirm-unwatch-top": "Да ја отстранам страницава од набљудуваните?",
"confirm-rollback-button": "ОК",
"confirm-rollback-top": "Да ги отповикам уредувањата на страницава?",
+ "confirm-mcrundo-title": "Откажи промена",
+ "mcrundofailed": "Откажувањето не успеа",
+ "mcrundo-missingparam": "Недостасуваат задолжителни параметри за барањето.",
+ "mcrundo-changed": "Страницата е изменета откако ги гледавте разликите. Прегледајте ја новата промена.",
"percent": "$1 %",
"quotation-marks": "„$1“",
"imgmultipageprev": "← претходна страница",
"thursday": "ꯁꯥꯒꯣꯜꯁꯦꯟ",
"friday": "ꯏꯔꯥꯏ",
"saturday": "ꯊꯥꯡꯖꯥ",
- "sun": "ê¯\85ꯨê¯\83ê¯¤ê¯ ",
+ "sun": "ê¯\85ꯣꯡ",
"mon": "ꯅꯤꯡ",
"tue": "ꯂꯩ",
"wed": "ꯌꯨꯝ",
"tool-link-emailuser": "Email this {{GENDER:$1|user}}",
"imagepage": "File lamai du ootlu",
"mediawikipage": "ꯄꯥꯎꯖꯦꯜꯒꯤ ꯂꯥꯃꯥꯏꯗꯨ ꯎꯨꯠꯂꯨ",
- "templatepage": "ê¯\87ꯦê¯\9dê¯\84ê¯\82ê¯¦ê¯ ê¯\80ꯤ ê¯\82ꯥê¯\83ꯥê¯\8fê¯\97ꯨ ê¯\8eê¯¨ê¯ ê¯\82ꯨ",
+ "templatepage": "ꯇꯦꯝꯄꯂꯦꯠꯀꯤ ꯂꯃꯥꯏꯗꯨ ꯎꯨꯠꯂꯨ",
"viewhelppage": "ꯃꯇꯦꯡ ꯄꯥꯡꯅꯕꯒꯤ ꯂꯥꯃꯥꯏꯗꯨ ꯎꯨꯠꯂꯨ",
- "categorypage": "Macahkhaiba lamai oootlooo",
+ "categorypage": "ꯃꯆꯥꯈꯥꯏꯕ ꯂꯃꯥꯏꯗꯨ ꯎꯨꯠꯂꯨ",
"viewtalkpage": "ꯈꯟꯅꯥ ꯅꯩꯅꯕꯗꯨ ꯎꯨꯠꯂꯨ",
"otherlanguages": "ꯑꯇꯣꯞꯄꯥ ꯂꯣꯟꯁꯤꯡꯗꯥ",
"redirectedfrom": "(Redirected from $1)",
"edithelp": "ꯁꯦꯝꯒꯠꯅꯕꯥ ꯃꯥꯇꯦꯡ",
"helppage-top-gethelp": "ꯃꯥꯇꯦꯡ",
"mainpage": "ꯃꯔꯨꯑꯣꯏꯕ ꯂꯃꯥꯏ",
- "mainpage-description": "ꯃꯔꯨ ꯑꯣꯏꯕꯥ ꯂꯃꯥꯏ",
+ "mainpage-description": "ꯃꯔꯨꯑꯣꯏꯕ ꯂꯃꯥꯏ",
"policy-url": "Project:ꯈꯣꯡꯊꯥꯡ",
"portal": "ꯃꯤꯌꯥꯝꯒꯤ ꯄꯣꯔꯇꯦꯜ",
"portal-url": "Project:ꯃꯤꯌꯥꯝꯒꯤ ꯄꯣꯔꯇꯦꯜ",
"newmessageslinkplural": "{{PLURAL:$1|a new message|999=new messages}}",
"newmessagesdifflinkplural": "ꯑꯔꯣꯏꯕꯥ {{PLURAL:$1|change|999=changes}}",
"youhavenewmessagesmulti": "$1 ꯅꯪꯒꯤ ꯑꯅꯧꯕꯥ ꯃꯦꯁꯦꯁ",
- "editsection": "ꯁꯦꯝꯒꯠꯄꯥ",
- "editold": "ꯁꯦꯝꯒꯠꯄꯥ",
+ "editsection": "ꯁꯦꯝꯒꯠꯄ",
+ "editold": "ꯁꯦꯝꯒꯠꯄ",
"viewsourceold": "ꯍꯧꯔꯛꯐꯝ ꯎꯨꯇꯂꯨ",
"editlink": "ꯁꯦꯝꯒꯠꯄꯥ",
"viewsourcelink": "ꯍꯧꯔꯛꯐꯝ ꯎꯨꯇꯂꯨ",
"editsectionhint": "ꯁꯦꯝꯒꯠꯄꯒꯤ ꯁꯔꯨꯛ: $1",
"toc": "ꯑꯌꯥꯎꯕꯥ",
"showtoc": "ꯎꯨꯠꯂꯨ",
- "hidetoc": "ꯂꯣꯇꯄꯥ",
+ "hidetoc": "ꯂꯣꯇꯄ",
"collapsible-collapse": "ꯁꯨꯞꯆꯤꯟꯕꯥ",
- "collapsible-expand": "ꯄꯥꯛꯊꯣꯛꯄꯥ",
+ "collapsible-expand": "ꯄꯥꯛꯊꯣꯛꯄ",
"confirmable-confirm": "Are {{GENDER:$1|you}} sure?",
"confirmable-yes": "ꯍꯣꯏ",
"confirmable-no": "ꯅꯠꯇꯦ",
"red-link-title": "$1 ꯂꯃꯥꯏꯗꯨ ꯂꯩꯇꯔꯦ",
"sort-descending": "ꯑꯇꯦꯟꯕꯥ ꯍꯟꯊꯔꯛꯂꯤꯕꯥ",
"sort-ascending": "ꯑꯇꯦꯟꯕꯥ ꯍꯦꯟꯒꯠꯂꯛꯂꯤꯕꯥ",
- "nstab-main": "ê¯\82ꯥê¯\83ꯥê¯\8f",
- "nstab-user": "Sijinnariba Lamai",
- "nstab-media": "ꯃꯦꯗꯤꯌꯥꯒꯤ ꯂꯥꯃꯥꯏ",
- "nstab-special": "MediaWiki:Bs-wikiadmin-mediawiki-akhannaba-lamai-text/mni",
- "nstab-project": "ê¯\84ꯥꯡê¯\8aꯣê¯\9bê¯\80ê¯\97ê¯\95ꯥ ê¯\82ꯥê¯\83ꯥê¯\8f",
+ "nstab-main": "ꯂꯃꯥꯏ",
+ "nstab-user": "ꯁꯤꯖꯤꯟꯅꯔꯤꯕ ꯂꯃꯥꯏ",
+ "nstab-media": "ꯃꯦꯗꯤꯌꯥ ꯂꯃꯥꯏ",
+ "nstab-special": "ꯑꯈꯟꯅꯕ ꯂꯃꯥꯏ",
+ "nstab-project": "ꯄꯥꯡꯊꯣꯛꯀꯗꯕꯥ ꯂꯃꯥꯏ",
"nstab-image": "ꯈꯣꯝꯖꯤꯟꯗꯨꯅꯥ ꯍꯥꯞꯐꯝ",
"nstab-mediawiki": "ꯄꯥꯎꯖꯦꯜ",
"nstab-template": "ꯇꯦꯝꯄꯂꯦꯠ",
"about": "အကြောင်း",
"article": "မာတိကာစာမျက်နှာ",
"newwindow": "(ဝင်းဒိုးအသစ်တစ်ခုတွင် ဖွင့်ရန်)",
- "cancel": "မလုပ်တော့",
+ "cancel": "မလုပ်တော့ပါ",
"moredotdotdot": "နောက်ထပ်...",
"morenotlisted": "ဤစာရင်းမှာ မပြည့်စုံနိုင်ပါ။",
"mypage": "စာမျက်နှာ",
"updatedmarker": "နောက်ဆုံးကြည့်ပြီးသည့်နောက်ပိုင်း တည်းဖြတ်ထားသည်။",
"printableversion": "ပရင့်ထုတ်နိုင်သော ဗားရှင်း",
"permalink": "ပုံသေလိပ်စာ",
- "print": "ပရင့်",
+ "print": "ပရင့်ထုတ်",
"view": "ကြည့်ရန်",
"view-foreign": "$1 တွင် ကြည့်ရန်",
"edit": "ပြင်ဆင်ရန်",
"yourpasswordagain": "စကားဝှက် ပြန်ရိုက်ပါ -",
"createacct-yourpasswordagain": "စကားဝှက်ကို အတည်ပြုပါ",
"createacct-yourpasswordagain-ph": "စကားဝှက်ကို ထပ်မံ ရိုက်ထည့်ပါ",
- "userlogin-remembermypassword": "Log in ဝင်ထားမည်",
+ "userlogin-remembermypassword": "အကောင့်ထဲ ဝင်ထားမည်",
"userlogin-signwithsecure": "လုံခြုံသော ဆက်သွယ်မှုကို သုံးမည်",
"cannotlogin-title": "လော့ဂ်အင် မဝင်ရောက်နိုင်ပါ",
"cannotlogin-text": "အကောင့်ထဲ ဝင်ရောက်ခြင်းမှာ မဖြစ်နိုင်ပါ",
"userlogin-resetpassword-link": "စကားဝှက် မေ့နေသလား။",
"userlogin-helplink2": "log in အကူအညီ",
"userlogin-loggedin": "သင်သည် {{GENDER:$1|$1}} အနေဖြင့် လော့အင်ဝင်ထားပြီး ဖြစ်သည်။ အခြားအသုံးပြုသူ အနေဖြင့် ဝင်ရောက်ရန် အောက်ပါပုံစံကို အသုံးပြုပါ။",
- "userlogin-reauth": "သင်သည် {{GENDER:$1|}} ဖြစ်ကြောင်း အတည်ပြုရန်အတွက် အကောင့်ထဲ ထပ်မံဝင်ရောက်ရပါမည်။",
+ "userlogin-reauth": "သင် {{GENDER:$1|}}ဖြစ်ကြောင်း အတည်ပြုရန်အတွက် အကောင့်ထဲ ထပ်မံဝင်ရောက်ရပါမည်။",
"userlogin-createanother": "အခြားအကောင့် ဖန်တီးရန်",
"createacct-emailrequired": "အီးမေး လိပ်စာ",
"createacct-emailoptional": "အီးမေး လိပ်စာ (ဖြည့်လိုက)",
"headline_tip": "အဆင့် ၂ ခေါင်းစီး",
"nowiki_sample": "ဖောမတ်မလုပ်ထားသော စာများကို ဤနေရာတွင် ထည့်ရန်",
"nowiki_tip": "ဝီကီပုံစံ ဖော်မတ်များကို လျစ်လျူရှုရန်",
+ "image_sample": "ဥပမာ.jpg",
"image_tip": "Embedded ထည့်ထားသော ဖိုင်",
+ "media_sample": "ဥပမာ.ogg",
"media_tip": "ဖိုင်လင့်",
"sig_tip": "အချိန်ပါပြသော သင့်လက်မှတ်",
"hr_tip": "မျဉ်းလဲ (စိစစ်သုံးရန်)",
"summary": "အကျဉ်းချုပ် -",
- "subject": "အကြောင်းအရာ -",
+ "subject": "အကြောင်းအရာ:",
"minoredit": "အရေးမကြီးသော ပြင်ဆင်မှု ဖြစ်သည်",
"watchthis": "ဤစာမျက်နှာကို စောင့်ကြည့်ရန်",
"savearticle": "ဤစာမျက်နှာကို သိမ်းရန်",
"email-allow-new-users-label": "အသစ်စက်စက် အသုံးပြုသူများဆီမှ အီးမေးလ်လက်ခံရန် ခွင့်ပြုမည်",
"email-blacklist-label": "ဤအသုံးပြုသူများ မိမိအား အီးမေးလ်ပို့ခြင်းကို ပိတ်ထားမည်",
"prefs-searchoptions": "ရှာဖွေရန်",
- "prefs-namespaces": "အမည်ညွှန်း",
+ "prefs-namespaces": "အမည်ညွှန်းများ",
"default": "ပုံမှန်အားဖြင့်",
"prefs-files": "ဖိုင်များ",
"prefs-custom-css": "စိတ်ကြိုက် CSS",
"recentchanges-label-plusminus": "စာမျက်နှာ အရွယ်အစားမှာ အောက်ပါ ဘိုက်ပမာဏ ပြောင်းလဲသွားခဲ့သည်",
"recentchanges-legend-heading": "<strong>အညွှန်း:</strong>",
"recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|စာမျက်နှာသစ်များ စာရင်း]]ကိုလည်း ကြည့်ရန်)",
+ "recentchanges-legend-plusminus": "(<em>±၁၂၃</em>)",
"recentchanges-submit": "ပြသရန်",
"rcfilters-tag-remove": "'$1' ကို ဖယ်ရှားရန်",
"rcfilters-legend-heading": "<strong>အတိုကောက်များ စာရင်း:</strong>",
"rcfilters-highlighted-filters-list": "မီးမောင်းထိုးပြထားသည်: $1",
"rcfilters-quickfilters": "သိမ်းထားသော စိစစ်မှုများ",
"rcfilters-quickfilters-placeholder-title": "မည်သည့် စိစစ်မှုမှ မသိမ်းရသေးပါ",
+ "rcfilters-quickfilters-placeholder-description": "သင်၏စိစစ်မှု အပြင်အဆင်များကို သိမ်းဆည်းရန်နှင့် နောင်အခါပြန်အသုံးပြုရန်အတွက် သက်ဝင်နေသော စိစစ်မှုဧရိယာရှိ bookmark အိုင်ကွန်ကို ကလစ်ပါ။",
"rcfilters-savedqueries-defaultlabel": "သိမ်းထားသော စိစစ်မှုများ",
"rcfilters-savedqueries-rename": "အမည်ပြန်ပြောင်းရန်",
"rcfilters-savedqueries-setdefault": "မူလပုံသေအဖြစ် သတ်မှတ်ရန်",
"listfiles_size": "အရွယ်အစား",
"listfiles_description": "ဖော်ပြချက်",
"listfiles_count": "ဗားရှင်းများ",
+ "listfiles-show-all": "ဖိုင်များ၏ ဗားရှင်းဟောင်း အပါအဝင်",
"listfiles-latestversion": "လက်ရှိဗားရှင်း",
"listfiles-latestversion-yes": "မှန်",
"listfiles-latestversion-no": "မဟုတ်",
"sharedupload-desc-create": "ဤဖိုင်သည် $1 မှဖြစ်ပြီး အခြားပရောဂျက်များတွင်လည်း အသုံးပြုနိုင်သည်။ [$2 ဖိုင်ဖော်ပြချက် စာမျက်နှာ]ပေါ်ရှိ ဖော်ပြချက်ကို တည်းဖြတ်နိုင်သည်။",
"filepage-nofile": "ဤအမည်ဖြင့် မည်သည့်ဖိုင်မှ မရှိပါ။",
"filepage-nofile-link": "ဤအမည်ဖြင့် မည်သည့်ဖိုင်မှ မရှိပါ။ သိုရာတွင် ယင်းကို [$1 upload တင်]နိုင်သည်။",
- "uploadnewversion-linktext": "ဤဖိုင်၏ နောက်ဆုံး version ကို upload တင်ရန်",
+ "uploadnewversion-linktext": "ဤဖိုင်၏ နောက်ဆုံးဗာရှင်းကို အပ်လုပ်တင်ရန်",
"shared-repo-from": "$1 ထံမှ",
"shared-repo-name-wikimediacommons": "ဝီကီမီဒီယာ ကွန်မွန်းစ်",
"upload-disallowed-here": "သင်သည် ဤဖိုင်အား ထပ်၍ ရေးသားမရနိုင်ပါ။",
"filerevert-submit": "ပြောင်းရန်",
"filedelete": "$1 ကိုဖျက်ပါ",
"filedelete-legend": "ဖိုင်ကိုဖျက်ပါ",
+ "filedelete-intro": "သင်သည် <strong>[[Media:$1|$1]]</strong> ဖိုင်အား ယင်း၏ ရာဇဝင်နှင့်တကွ ဖျက်ပစ်တော့မည် ဖြစ်သည်။",
"filedelete-comment": "အကြောင်းပြချက်:",
"filedelete-submit": "ဖျက်",
"filedelete-success": "'''$1''' ကို ဖျက်ပစ်လိုက်ပြီ။",
"checkbox-select": "ရွေးချယ်: $1",
"checkbox-all": "အားလုံး",
"checkbox-none": "ဘာမှမရှိ",
+ "checkbox-invert": "ပြောင်းပြန်",
"allpages": "စာမျက်နှာအားလုံး",
"nextpage": "နောက်ထပ်စာမျက်နှာ ($1)",
"prevpage": "ယခင် စာမျက်နှာ ($1)",
"allmessages": "စနစ်၏ သတင်းများ",
"allmessagesname": "အမည်",
"allmessagesdefault": "ပုံမှန် အသိပေးချက် စာသား",
+ "allmessagescurrent": "လက်ရှိ မက်ဆေ့စာသား",
"allmessages-filter-legend": "စစ်ထုတ်ခြင်း",
"allmessages-filter-unmodified": "မပြုပြင်ထားသော",
"allmessages-filter-all": "အားလုံး",
"show-big-image-other": "အခြား {{PLURAL:$2|ပုံရိပ်ပြတ်သားမှု|ပုံရိပ်ပြတ်သားမှု}}: $1။",
"show-big-image-size": "$1 × $2 ပစ်ဇယ်",
"newimages": "ပုံအသစ်များပြခန်း",
+ "newimages-summary": "ဤအထူးစာမျက်နှာမှာ နောက်ဆုံးတင်ထားသော ဖိုင်များကို ပြသပေးသည်။",
"newimages-legend": "စိစစ်မှု",
"newimages-label": "ဖိုင်အမည် (သို့ ယင်း၏အစိတ်အပိုင်း) -",
"newimages-user": "အိုင်ပီလိပ်စာ သို့ အသုံးပြုသူအမည်",
"newimages-newbies": "အကောင့်အသစ်များ၏ ပံ့ပိုးမှုများကိုသာ ပြရန်",
+ "newimages-showbots": "ဘော့များ တင်ထားသည်ကို ပြရန်",
"newimages-mediatype": "မီဒီယာ အမျိုးအစား:",
"noimages": "ကြည့်စရာဘာမှ မရှိပါ။",
"ilsubmit": "ရှာဖွေရန်",
"exif-datetimedigitized": "ဒီဂျစ်တယ်ပြောင်းသည့် နေ့ရက်နှင့် အချိန်",
"exif-exposuretime-format": "$1 စက္ကန့် ($2)",
"exif-shutterspeedvalue": "APEX ရှပ်တာ အမြန်နှုန်း",
+ "exif-lightsource": "အလင်းရင်းမြစ်",
"exif-flash": "ဖလက်ရှ်",
"exif-filesource": "ဖိုင်ရင်းမြစ်",
"exif-gpslatitude": "လတ္တီကျု",
"exif-gpslongitude": "လောင်ဂျီကျု",
"exif-gpsaltitude": "အမြင့်",
+ "exif-gpstimestamp": "ဂျီပီအက်စ်အချိန် (အက်တော့မစ် နာရီ)",
"exif-gpsimgdirection": "ရုပ်ပုံ၏ လမ်းကြောင်း",
"exif-gpsdatestamp": "ဂျီပီအက်စ်ရက်စွဲ",
"exif-objectname": "ခေါင်းစဉ်တို",
"exif-lightsource-9": "ကောင်းမွန်သော ရာသီဥတု",
"exif-lightsource-10": "တိမ်ထူသော ရာသီဥတု",
"exif-lightsource-11": "အရိပ်",
+ "exif-lightsource-255": "အခြား အလင်းရင်းမြစ်",
"exif-focalplaneresolutionunit-2": "လက်မှတ်",
"exif-sensingmethod-1": "မသတ်မှတ်ထားသော",
"exif-scenecapturetype-3": "ညနေပုံ",
"tag-mw-undo": "နောက်ပြန် ပြန်ပြင်ခြင်း",
"tags-title": "အမည်တွဲများ",
"tags-tag": "အမည်တွဲ အမည်",
+ "tags-description-header": "ဆိုလိုရင်းအဓိပ္ပာယ် အပြည့်အစုံ",
+ "tags-source-header": "ရင်းမြစ်",
"tags-active-yes": "မှန်",
"tags-active-no": "မလုပ်ပါ",
"tags-source-extension": "ဆော့ဝဲလ်မှ သတ်မှတ်ထားသော",
"htmlform-submit": "ထည့်သွင်းရန်",
"htmlform-reset": "ပြောင်းလဲထားသည်များ မလုပ်တော့ရန်",
"htmlform-selectorother-other": "အခြား",
+ "htmlform-no": "မလုပ်ပါ",
"htmlform-chosen-placeholder": "လုပ်ဆောင်ချက်တစ်ခု ရွေးချယ်ရန်",
"htmlform-cloner-create": "ပို၍ ထပ်ပေါင်းရန်",
"htmlform-cloner-delete": "ဖယ်ရှားရန်",
"log-action-filter-rights": "အခွင့်အရေးပြောင်းလဲမှု အမျိုးအစား:",
"log-action-filter-all": "အားလုံး",
"log-action-filter-block-block": "ပိတ်ပင်",
+ "log-action-filter-block-unblock": "မပိတ်ပင်တော့ရန်",
"log-action-filter-contentmodel-change": "မာတိကာမော်ဒယ်၏ ပြောင်းလဲမှု",
"log-action-filter-delete-delete": "စာမျက်နှာ ဖျက်ပစ်ခြင်း",
"log-action-filter-delete-event": "မှတ်တမ်း ဖျက်ပစ်ခြင်း",
"ns-specialprotected": "'E ppaggene spiciale nun se ponno cagnà.",
"titleprotected": "'A criazione 'e stu titolo è stata bloccata 'a ll'utente [[User:$1|$1]].\n'A ragione è chesta: <em>$2</em>.",
"filereadonlyerror": "Nun se può cagnà 'o file \"$1\" pecché 'o repository 'e file \"$2\" sta 'n modo sulo-lettura.\n\nL'ammenistratore 'e sistema che l'ave arrestato ha dato sta ragione: \"$3\".",
+ "invalidtitle": "Titolo invalido",
"invalidtitle-knownnamespace": "Titolo nun buono c' 'o namespace \"$2\" e testo \"$3\"",
"invalidtitle-unknownnamespace": "Titolo nun buono c' 'o namespace scanusciuto \"$1\" e testo \"$2\"",
"exception-nologin": "Acciesso nun affettuato",
"resetpass-submit-loggedin": "Cagna password",
"resetpass-submit-cancel": "Scancella",
"resetpass-wrong-oldpass": "'A password temporanea o attuale nun è bbona.\n'A password putesse avé cagnato, o pure s'è addimannata na password temporanea nova.",
- "resetpass-recycled": "Pe piacere riabbiate 'a password e mettete na password differénte a chella 'e mmò.",
+ "resetpass-recycled": "Pe piacere cagnat 'a password e mettete na password differénte a chella 'e mmò.",
"resetpass-temp-emailed": "Sì trasuto cu nu codece temporaneo, mannato via e-mail. Pe' fà cumpleta 'a riggistraziona, avite 'e abbià na password nova ccà:",
"resetpass-temp-password": "Password temporanea:",
"resetpass-abort-generic": "'O cagnamiento d' 'a password s'è spezzato 'a na stensione.",
"resetpass-expired": "'A pasword è ammaturata. Avite 'e ffà na password nova pe putè trasì.",
- "resetpass-expired-soft": "'A pasword toja è ammaturata e s'adda riabbià. Avite 'e scegliere na password nova mò, o ffà click ncopp'a \"{{int:authprovider-resetpass-skip-label}}\" p' 'a riabbià aroppo.",
- "resetpass-validity-soft": "'A password toja nun è bbona: $1\n\nAvite 'e scegliere na password nova mò, o ffà click ncopp'a \"{{int:authprovider-resetpass-skip-label}}\" p' 'a riabbià aròppo.",
+ "resetpass-expired-soft": "'A pasword vuost è ammaturata e s'adda cagnà. Avite 'e scegliere na password nova mò, o ffà click ncopp'a \"{{int:authprovider-resetpass-skip-label}}\" p' 'a cagnà aroppo.",
+ "resetpass-validity-soft": "'A password toja nun è bbona: $1\n\nAvite 'e scegliere na password nova mò, o ffà click ncopp'a \"{{int:authprovider-resetpass-skip-label}}\" p' 'a cagnà aròppo.",
"passwordreset": "Riabbìa 'a password",
"passwordreset-text-one": "Ghienche stu modulo pe' ricevere na mmasciata e-mail c' 'a password temporanea.",
"passwordreset-text-many": "{{PLURAL:$1|Ghienche uno d' 'e campe pe' ricevere na password temporanea cu na mmasciata e-mail.}}",
"parser-template-loop-warning": "È stato scummigliato n'aniello d' 'o template: [[$1]]",
"template-loop-category": "Paggene ca chiammassero a esse stisse",
"template-loop-category-desc": "Sta paggena tenesse nu template ca chiammasse a essa stissa, cioè nu template addò sta mmescat' 'o template ca 'o chiammasse.",
+ "template-loop-warning": "<strong>Attenziò:</strong> sta paggena chiammass' a [[:$1]] e stu fatto 'a facess addeventà nu loop (na chiammata infinita d' 'o template).",
"parser-template-recursion-depth-warning": "È arrivato 'o lemmeto 'e ricurzione d' 'o template ($1)",
"language-converter-depth-warning": "'O fùto d' 'o lemmeto d' 'o scagnatòre 'e lengua è appassato ($1)",
"node-count-exceeded-category": "Paggene addò 'o nummero 'e núrece è stato appassato",
"expansion-depth-exceeded-warning": "Sta paggena ha appassato 'o lemmeto 'e futo 'e spansione",
"parser-unstrip-loop-warning": "Scummigliato aniello Unstrip",
"unstrip-depth-warning": "Appassato 'o lémmeto 'e ricurzione d' Unstrip ($1)",
+ "unstrip-depth-category": "Paggene addò ll' unstrip depth limit è assaje for o limmeto",
+ "unstrip-size-warning": "Appassato 'o lémmeto 'e gruosso d' Unstrip ($1)",
+ "unstrip-size-category": "Paggene addò 'o lémmeto 'e gruosso e unstrip è appassatt",
"converter-manual-rule-error": "È stato scummigliato n'errore dint'a regola manuale 'e converziona 'e lengua",
"undo-success": "'O cagnamiento se può annullà.\nPe' piacere vedete 'e differenze mmustate nfra 'e verziune pe' te ffà capace ca 'e cuntenute songo bbuone, e astipate 'e cagnamiente ccà abbascio pe' fernì e accussì turnà arreto.",
"undo-failure": "Nun se può fà turnà arreto 'o cagnamiento pecché ce sta nu conflitto ch' 'e cagnamiente intermedie.",
+ "undo-main-slot-only": "Stu cagnamento nun se pò turnà arreto pecché ce vulessero 'e cuntenute for' 'o main slot.",
"undo-norev": "Nun se può fà turnà arreto 'o cagnamiento pecché nun esiste o s'è scancellato.",
"undo-nochange": "Pare ca sto cagnamiento s'ha scancellato già.",
"undo-summary": "Scancella 'o càgno $1 'e [[Special:Contributions/$2|$2]] ([[User talk:$2|Chiàcchiera]])",
"diff-multi-sameuser": "({{PLURAL:$1|Na verziona ntermedia|$1 verziune ntermedie}} 'e n'utente stisso nun {{PLURAL:$1|è mmustata|songo mmustate}})",
"diff-multi-otherusers": "({{PLURAL:$1|Na virzione ntermedia|$1 verziune ntermedie}} 'a {{PLURAL:$2|n'at'utente|$2 n'ati ddoj'utente}} nun è mmustata)",
"diff-multi-manyusers": "({{PLURAL:$1|Na virzione ntermedia|$1 verziune ntermedie}} 'a cchiù 'e $2 {{PLURAL:$2|utente|utente}} nun è mmustata)",
+ "diff-paragraph-moved-tonew": "'O paragrafo è stato spustat. Facite clic pe' puté cagnà dint'a nova posiziona.",
"difference-missing-revision": "{{PLURAL:$2|Na virziona|$2 verziune}} 'e sta differenza ($1) {{PLURAL:$2|nun è stata truvata|nun so' state truvate}}.\n\nChest'è succiesso quanno s'è secutato nu diff obsoleto a na paggena scancellata.\n'E dettaglie se ponno truvà dint'a [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} 'o riggistro 'e scancellamiente].",
"searchresults": "Risultato d''a recerca",
"searchresults-title": "Ascià risultate ppe \"$1\"",
"recentchangeslinked-page": "Sidenavn:",
"recentchangeslinked-to": "Vis endringer på sider som lenker til den gitte siden istedet",
"recentchanges-page-added-to-category": "[[:$1]] ble lagt til i kategorien",
- "recentchanges-page-added-to-category-bundled": "[[:$1]] lagt til i kategori, [[Special:WhatLinksHere/$1|denne siden er inkludert i andre sider]]",
+ "recentchanges-page-added-to-category-bundled": "[[:$1]] lagt til i kategorien; [[Special:WhatLinksHere/$1|denne siden er inkludert i andre sider]]",
"recentchanges-page-removed-from-category": "[[:$1]] fjernet fra kategori",
"recentchanges-page-removed-from-category-bundled": "[[:$1]] fjernet fra kategori, [[Special:WhatLinksHere/$1|denne siden er inkludert i andre sider]]",
"autochange-username": "Automatisk MediaWiki-endring",
"logout": "Ofmelden",
"userlogout": "Aofmelden",
"notloggedin": "Neet an-emelded",
- "userlogin-noaccount": "Heb jy noch gyn gebrukersname?",
+ "userlogin-noaccount": "Heb jy noch geen gebrukersname?",
"userlogin-joinproject": "Wörd lid van {{SITENAME}}",
"createaccount": "Inskryven",
"userlogin-resetpassword-link": "Juuw wachtwoord vergeaten?",
"rcfilters-days-show-days": "$1 {{PLURAL:$1|dag|dagen}}",
"rcfilters-days-show-hours": "$1 {{PLURAL:$1|uur|uren}}",
"rcfilters-quickfilters": "Up-eslöägen filters",
- "rcfilters-quickfilters-placeholder-title": "Noch gyn filters up-eslöägen",
+ "rcfilters-quickfilters-placeholder-title": "Noch geen filters up-eslöägen",
"rcfilters-quickfilters-placeholder-description": "Üm juuw filterinstellingen up te slån en et låter te gebruken, klik up et bladwyserikoon underan by \"Aktive filters\".",
"rcfilters-savedqueries-apply-label": "Instellingen opslaon",
"rcfilters-savedqueries-cancel-label": "Aofbreken",
"rcfilters-restore-default-filters": "Standardfilters weerummezetten",
"rcfilters-clear-all-filters": "Alle filters vortdoon",
"rcfilters-show-new-changes": "Låt nyste wysigingen seen",
- "rcfilters-search-placeholder": "Filter wysigingen (gebruuk et menü of söök up filtername)",
+ "rcfilters-search-placeholder": "Filter wysigingen (gebruuk et menu of söök up filtername)",
"rcfilters-filterlist-feedbacklink": "Låt uns weaten wat jy van disse (nye) filterhülpmiddels vinden",
"rcfilters-highlightbutton-title": "Resultåten markeren",
"rcfilters-highlightmenu-title": "Kies n kleur",
"protect-summary-cascade": "kaskade",
"protect-expiring": "löp aof op $1 (UTC)",
"protect-expiring-local": "vervölt op $1",
- "protect-expiry-indefinite": "onbepark",
+ "protect-expiry-indefinite": "unbetöänd",
"protect-cascade": "Kaskadebeveiliging (beveilig alle ziejen en mallen die in disse zied op-eneumen bin)",
"protect-cantedit": "Je kunnen t beveiligingsnivo van disse zied niet wiezigen, umda'j gien rechten hebben um t te bewarken.",
"protect-othertime": "Aandere tiedsduur:",
"Ooswesthoesbes",
"MarcoSwart",
"Pahles",
- "Optilete"
+ "Optilete",
+ "Goefie"
]
},
"tog-underline": "Verwijzingen onderstrepen:",
"group-autoconfirmed": "autobevestigde gebruikers",
"group-bot": "bots",
"group-sysop": "beheerders",
- "group-interface-admin": "interfacemoderatoren",
+ "group-interface-admin": "interfacebeheerders",
"group-bureaucrat": "bureaucraten",
"group-suppress": "toezichthouders",
"group-all": "(iedereen)",
"filehist-comment": "Opmerking",
"imagelinks": "Bestandsgebruik",
"linkstoimage": "Dit bestand wordt op de volgende {{PLURAL:$1|pagina|$1 pagina's}} gebruikt:",
- "linkstoimage-more": "Meer dan $1 {{PLURAL:$1|pagina gebruikt|pagina's gebruiken}} dit bestand.\nDe volgende lijst geeft alleen de eerste {{PLURAL:$1|pagina|$1 pagina's}} die dit bestand gebruiken weer.\nEr is ook een [[Special:WhatLinksHere/$2|volledige lijst]] beschikbaar.",
+ "linkstoimage-more": "Meer dan $1 {{PLURAL:$1|page uses|pagina's gebruiken}} dit bestand.\nDe volgende lijst geeft alleen de {{PLURAL:$1|first page|eerste $1 pagina's}} weer die dit bestand gebruiken.\nEr is ook een [[Special:WhatLinksHere/$2|volledige lijst]] beschikbaar.",
"nolinkstoimage": "Geen enkele pagina gebruikt dit bestand.",
"morelinkstoimage": "[[Special:WhatLinksHere/$1|Meer koppelingen]] naar dit bestand bekijken.",
"linkstoimage-redirect": "$1 (bestandsdoorverwijzing) $2",
"confirm-unwatch-top": "Deze pagina verwijderen uit uw volglijst?",
"confirm-rollback-button": "OK",
"confirm-rollback-top": "Bewerkingen op deze pagina ongedaan maken?",
+ "confirm-mcrundo-title": "Een wijziging ongedaan maken",
+ "mcrundofailed": "Ongedaan maken mislukt",
+ "mcrundo-missingparam": "Er ontbreken nodige parameters in het verzoek.",
"quotation-marks": "\"$1\"",
"imgmultipageprev": "← vorige pagina",
"imgmultipagenext": "volgende pagina →",
"rcfilters-watchlist-markseen-button": "Merk alle endringar som sette",
"rcfilters-watchlist-edit-watchlist-button": "Endra lista over sider du overvaker",
"rcfilters-watchlist-showupdated": "Sider du ikkje har vitja sidan dei vart endra er viste med <strong>feit</strong> skrift.",
+ "rcfilters-filter-showlinkedfrom-label": "Vis endringar på sider det vert lenkja til på sida",
+ "rcfilters-filter-showlinkedfrom-option-label": "<strong>Sider som det vert lenkja til på</strong> den valde sida",
+ "rcfilters-filter-showlinkedto-label": "Vis endringar på sider som lenkjar til sida",
+ "rcfilters-filter-showlinkedto-option-label": "<strong>Sider som lenkjar til</strong> den valde sida",
"rcnotefrom": "Nedanfor er endringane gjorde sidan <strong>$2</strong> viste (opp til <strong>$1</strong> stykke)",
"rclistfromreset": "Nullstill datoval",
"rclistfrom": "Vis nye endringar sidan $3 $2",
"copyrightwarning2": "Wszelki wkład na {{SITENAME}} może być edytowany, zmieniany lub usunięty przez innych użytkowników.\nJeśli nie chcesz, żeby Twój tekst był dowolnie zmieniany przez każdego i rozpowszechniany bez ograniczeń, nie umieszczaj go tutaj.<br />\nZapisując swoją edycję, oświadczasz, że ten tekst jest Twoim dziełem lub pochodzi z materiałów dostępnych na warunkach ''domeny publicznej'' lub kompatybilnych (zobacz także $1).\n'''PROSZĘ NIE WPROWADZAĆ MATERIAŁÓW CHRONIONYCH PRAWEM AUTORSKIM BEZ POZWOLENIA WŁAŚCICIELA!'''",
"editpage-cannot-use-custom-model": "Model zawartości tej strony nie może być zmieniony.",
"longpageerror": "'''Błąd! Wprowadzony przez Ciebie tekst ma {{PLURAL:$1|1 kilobajt|$1 kilobajty|$1 kilobajtów}}. Długość tekstu nie może przekraczać {{PLURAL:$2|1 kilobajt|$2 kilobajty|$2 kilobajtów}}. Tekst nie może być zapisany.'''",
- "readonlywarning": "<strong>Uwaga! Baza danych została zablokowana do celów administracyjnych. W tej chwili nie można zapisać nowej wersji strony. Jeśli chcesz, możesz skopiować ją do pliku, aby móc zapisać ją później.</strong>\n\nAdministrator systemu, który zablokował bazę, podał następujący powód: $1",
+ "readonlywarning": "<strong>Uwaga! Baza danych została zablokowana w celach konserwacyjnych i w tej chwili nie można zapisać nowej wersji strony. Jeśli chcesz, możesz skopiować ją do pliku, aby móc zapisać ją później.</strong>\n\nAdministrator systemu, który zablokował bazę, podał następujący powód: $1",
"protectedpagewarning": "'''Uwaga! Możliwość modyfikacji tej strony została zabezpieczona. Mogą ją edytować jedynie użytkownicy z uprawnieniami administratora.'''\nOstatni wpis z rejestru jest pokazany poniżej.",
"semiprotectedpagewarning": "<strong>Uwaga:</strong> Ta strona została zabezpieczona i tylko zarejestrowani użytkownicy mogą ją edytować.\nOstatni wpis z rejestru jest pokazany poniżej:",
"cascadeprotectedwarning": "<strong>Uwaga:</strong> Ta strona została zabezpieczona i tylko użytkownicy z [[Special:ListGroupRights|określonymi uprawnieniami]] mogą ją edytować. Została ona osadzona w {{PLURAL:$1|następującej stronie, która została zabezpieczona|następujących stronach, które zostały zabezpieczone}} z włączoną opcją dziedziczenia:",
"confirm-unwatch-top": "Remover esta página das páginas vigiadas?",
"confirm-rollback-button": "OK",
"confirm-rollback-top": "Reverter edições nesta página?",
+ "confirm-mcrundo-title": "Desfazer uma mudança",
+ "mcrundofailed": "A reversão falhou",
+ "mcrundo-missingparam": "Faltam parâmetros obrigatórios no pedido.",
+ "mcrundo-changed": "Esta página foi alterada desde que começou a ver as diferenças. Reveja a nova mudança, por favor.",
"semicolon-separator": "; ",
"comma-separator": ", ",
"colon-separator": ": ",
"confirm-unwatch-top": "Remover esta página da lista de páginas vigiadas?",
"confirm-rollback-button": "OK",
"confirm-rollback-top": "Reverter as edições desta página?",
+ "confirm-mcrundo-title": "Desfazer uma mudança",
+ "mcrundofailed": "A reversão falhou",
+ "mcrundo-missingparam": "Faltam parâmetros obrigatórios no pedido.",
+ "mcrundo-changed": "Esta página foi alterada desde que começou a ver as diferenças. Reveja a nova mudança, por favor.",
"quotation-marks": "\"$1\"",
"imgmultipageprev": "← página anterior",
"imgmultipagenext": "página seguinte →",
"confirm-unwatch-top": "Used as confirmation message.",
"confirm-rollback-button": "Used as Submit button text.\n{{Identical|OK}}",
"confirm-rollback-top": "Used as confirmation message.",
+ "confirm-mcrundo-title": "Title for the editless undo form.",
+ "mcrundofailed": "Title of the error page when an editless undo fails.",
+ "mcrundo-missingparam": "Error displayed when parameters for action=mcrundo are missing",
+ "mcrundo-changed": "Message displayed when the page has been edited between the loading and submission of an editless undo.",
"semicolon-separator": "{{optional}}",
"comma-separator": "{{optional}}\n\nWarning: languages have different usages of punctuation, and sometimes they are swapped (e.g. openining and closing quotation marks, or full stop and colon in Armenian), or change their form (the full stop in Chinese and Japanese, the prefered \"colon\" in Armenian used in fact as the regular full stop, the comma in Arabic, Armenian, and Chinese...)\n\nTheir spacing (before or after) may also vary across languages (for example French requires a non-breaking space, preferably narrow if the browser supports NNBSP, on the inner side of some punctuations like quotation/question/exclamation marks, colon, and semicolons).",
"colon-separator": "{{optional}}\nChange it only if your language uses another character for ':' or it needs an extra space before the colon.",
"edit-error-long": "Error message. Parameters:\n* $1 - the error details\nSee also:\n* {{msg-mw|edit-error-short}}\n{{Identical|Error}}",
"revid": "Used to format a revision ID number in text. Parameters:\n* $1 - Revision ID number.\n{{Identical|Revision}}",
"pageid": "Used to format a page ID number in text. Parameters:\n* $1 - Page ID number.",
- "interfaceadmin-info": "Used to wrap the normal permission error for actions which used to only require editinterface but now require more permissions. Parameters:\n* $1 - The normal permission error.",
+ "interfaceadmin-info": "Part of the error message shown when someone with the <code>editinterface</code> right but without the appropriate <code>editsite*</code> right tries to edit a sitewide CSS/JSON/JS page.",
"rawhtml-notallowed": "Error message given when $wgRawHtml = true; is set and a user uses an <html> tag in a system message or somewhere other than a normal page.",
"gotointerwiki": "{{doc-special|GoToInterwiki}}\n\nSpecial:GoToInterwiki is a warning page displayed before redirecting users to external interwiki links. Its triggered by people going to something like [[Special:Search/google:foo]].\n{{Identical|Leaving}}",
"gotointerwiki-invalid": "Message shown on Special:GoToInterwiki if given an invalid title.",
"Vcohen",
"AttemptToCallNil",
"Stjn",
- "Vlad5250"
+ "Vlad5250",
+ "Marshmallych",
+ "Atsirlin"
]
},
"tog-underline": "Подчёркивание ссылок:",
"tog-watchdefault": "Добавлять в список наблюдения изменённые мной страницы и описания файлов",
"tog-watchmoves": "Добавлять в список наблюдения переименованные мной страницы и файлы",
"tog-watchdeletion": "Добавлять в список наблюдения удалённые мной страницы и файлы",
- "tog-watchuploads": "Ð\94обавлÑ\8fÑ\82Ñ\8c закаÑ\87анные мною файлы в список наблюдения",
+ "tog-watchuploads": "Ð\94обавлÑ\8fÑ\82Ñ\8c загÑ\80Ñ\83женные мною файлы в список наблюдения",
"tog-watchrollback": "Добавлять страницы, где я выполнил откат, в мой список наблюдения",
"tog-minordefault": "По умолчанию помечать правки как малые",
"tog-previewontop": "Помещать предпросмотр перед окном редактирования",
"redirectedfrom": "(перенаправлено с «$1»)",
"redirectpagesub": "Страница-перенаправление",
"redirectto": "Перенаправление на:",
- "lastmodifiedat": "ÐÑ\82а Ñ\81Ñ\82Ñ\80аниÑ\86а поÑ\81ледний Ñ\80аз бÑ\8bла оÑ\82Ñ\80едакÑ\82иÑ\80ована $1 в $2.",
+ "lastmodifiedat": "Ð\92 поÑ\81ледний Ñ\80аз Ñ\8dÑ\82а Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\80едакÑ\82иÑ\80овалаÑ\81Ñ\8c $1, в $2.",
"viewcount": "К этой странице обращались $1 {{PLURAL:$1|раз|раза|раз}}.",
"protectedpage": "Защищённая страница",
"jumpto": "Перейти к:",
"jumptonavigation": "навигация",
"jumptosearch": "поиск",
- "view-pool-error": "Ð\98звиниÑ\82е, в наÑ\81Ñ\82оÑ\8fÑ\89ий моменÑ\82 Ñ\81еÑ\80веÑ\80Ñ\8b пеÑ\80егÑ\80Ñ\83женÑ\8b.\nÐÑ\82Ñ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 пÑ\8bÑ\82аÑ\8eÑ\82Ñ\81Ñ\8f одновÑ\80еменно пÑ\80оÑ\81моÑ\82Ñ\80еÑ\82Ñ\8c Ñ\81лиÑ\88ком многие.\nПожалуйста, подождите немного перед повторной попыткой обращения к этой странице.\n\n$1",
- "generic-pool-error": "Извините, в настоящий момент серверы перегружены.\nСлишком много пользователей пытаются просмотреть этот ресурс.\nПожалуйста, подождите и повторите попытку обращения к нему позже.",
+ "view-pool-error": "Ð\98звиниÑ\82е, в наÑ\81Ñ\82оÑ\8fÑ\89ий моменÑ\82 Ñ\81еÑ\80веÑ\80Ñ\8b пеÑ\80егÑ\80Ñ\83женÑ\8b.\nСлиÑ\88ком много Ñ\83Ñ\87аÑ\81Ñ\82ников пÑ\8bÑ\82аÑ\8eÑ\82Ñ\81Ñ\8f пÑ\80оÑ\81моÑ\82Ñ\80еÑ\82Ñ\8c еÑ\91 одновÑ\80еменно.\nПожалуйста, подождите немного перед повторной попыткой обращения к этой странице.\n\n$1",
+ "generic-pool-error": "Извините, в настоящий момент серверы перегружены.\nСлишком много участников пытаются просмотреть этот ресурс.\nПожалуйста, подождите и повторите попытку обращения к нему позже.",
"pool-timeout": "Истекло время ожидания блокировки",
- "pool-queuefull": "Ð\9dакопиÑ\82елÑ\8c запросов полон",
+ "pool-queuefull": "Ð\9fÑ\83л запросов полон",
"pool-errorunknown": "Неизвестная ошибка",
"pool-servererror": "Служба счётчика пула недоступна ($1).",
"poolcounter-usage-error": "Ошибка использования: $1",
"privacy": "Политика конфиденциальности",
"privacypage": "Project:Политика конфиденциальности",
"badaccess": "Ошибка доступа",
- "badaccess-group0": "Вы не можете выполнять запрошенное действие.",
- "badaccess-groups": "Ð\97апÑ\80оÑ\88енное дейÑ\81Ñ\82вие могÑ\83Ñ\82 вÑ\8bполнÑ\8fÑ\82Ñ\8c Ñ\82олÑ\8cко Ñ\83Ñ\87аÑ\81Ñ\82ники {{PLURAL:$2|1=из гÑ\80Ñ\83ппÑ\8b «$1»|одной из Ñ\81ледÑ\83Ñ\8eÑ\89иÑ\85 гÑ\80Ñ\83пп: $1}}",
+ "badaccess-group0": "Вы не можете выполнить запрошенное действие.",
+ "badaccess-groups": "Запрошенное действие могут выполнять участники {{PLURAL:$2|1=из группы «$1»|одной из следующих групп: $1}}",
"versionrequired": "Требуется MediaWiki версии $1",
- "versionrequiredtext": "Для работы с этой страницей требуется MediaWiki версии $1. См. [[Special:Version|информацию об программном обеспечении]].",
+ "versionrequiredtext": "Для работы с этой страницей требуется MediaWiki версии $1. См. [[Special:Version|информацию о программном обеспечении]].",
"ok": "OK",
"pagetitle": "$1 — {{SITENAME}}",
"pagetitle-view-mainpage": "{{SITENAME}}",
"nstab-help": "Справка",
"nstab-category": "Категория",
"mainpage-nstab": "Заглавная",
- "nosuchaction": "Такого дейÑ\81Ñ\82виÑ\8f неÑ\82",
+ "nosuchaction": "Ð\9dеÑ\82 Ñ\82акого дейÑ\81Ñ\82виÑ\8f",
"nosuchactiontext": "Указанное в URL действие ошибочно.\nВозможно, вы допустили опечатку при наборе URL или перешли по ошибочной ссылке.\nЭто может также указывать на ошибку в проекте {{SITENAME}}.",
"nosuchspecialpage": "Нет такой служебной страницы",
"nospecialpagetext": "<strong>Запрошенной вами служебной страницы не существует.</strong>\n\nСписок существующих служебных страниц: [[Special:SpecialPages|{{int:specialpages}}]].",
"databaseerror-query": "Запрос: $1",
"databaseerror-function": "Функция: $1",
"databaseerror-error": "Ошибка: $1",
- "transaction-duration-limit-exceeded": "Ð\94лÑ\8f Ñ\82ого, Ñ\87Ñ\82обÑ\8b избежаÑ\82Ñ\8c болÑ\8cÑ\88ого лага пÑ\80и Ñ\80епликаÑ\86ии, Ñ\8dÑ\82а Ñ\82Ñ\80анзакÑ\86иÑ\8f бÑ\8bла пÑ\80еÑ\80вана, поÑ\81колÑ\8cкÑ\83 пÑ\80одолжиÑ\82елÑ\8cноÑ\81Ñ\82Ñ\8c запиÑ\81и ($1) пÑ\80евÑ\8bÑ\81ила лимиÑ\82 в $2 {{PLURAL:$2|Ñ\81екÑ\83ндÑ\83|Ñ\81екÑ\83нд|Ñ\81екÑ\83ндÑ\8b}}.\nÐ\95Ñ\81ли вÑ\8b изменÑ\8fеÑ\82е неÑ\81колÑ\8cко Ñ\8dлеменÑ\82ов за один раз, попробуйте вместо этого сделать несколько небольших операций.",
+ "transaction-duration-limit-exceeded": "Ð\94лÑ\8f Ñ\82ого, Ñ\87Ñ\82обÑ\8b избежаÑ\82Ñ\8c болÑ\8cÑ\88ой задеÑ\80жки пÑ\80и Ñ\80епликаÑ\86ии, Ñ\8dÑ\82а Ñ\82Ñ\80анзакÑ\86иÑ\8f бÑ\8bла пÑ\80еÑ\80вана. Ð\9fÑ\80одолжиÑ\82елÑ\8cноÑ\81Ñ\82Ñ\8c запиÑ\81и ($1) пÑ\80евÑ\8bÑ\81ила лимиÑ\82 в $2 {{PLURAL:$2|Ñ\81екÑ\83ндÑ\83|Ñ\81екÑ\83нд|Ñ\81екÑ\83ндÑ\8b}}.\nÐ\95Ñ\81ли вÑ\8b изменÑ\8fеÑ\82е неÑ\81колÑ\8cко Ñ\8dлеменÑ\82ов за раз, попробуйте вместо этого сделать несколько небольших операций.",
"laggedslavemode": "<strong>Внимание:</strong> на странице могут отсутствовать последние обновления.",
"readonly": "Запись в базу данных заблокирована",
"enterlockreason": "Укажите причину и намеченный срок блокировки.",
- "readonlytext": "Добавление новых статей и другие изменения базы данных сейчас заблокированы: вероятно, в связи с плановым обслуживанием.\n\nСистемный администратор, заблокировавший базу, оставил следующее объяснение: $1",
+ "readonlytext": "Добавление новых статей и другие изменения базы данных сейчас заблокированы, вероятно, в связи с плановым обслуживанием.\n\nСистемный администратор, заблокировавший базу, оставил следующее объяснение: «$1».",
"missing-article": "В базе данных не найдено запрашиваемого текста страницы «$1» $2, который следовало найти.\n\nПодобная ситуация обычно возникает при попытке перехода по устаревшей ссылке на историю изменения страницы, которая была удалена.\n\nЕсли дело не в этом, то скорее всего, вы обнаружили ошибку в программном обеспечении.\nПожалуйста, сообщите об этом одному из [[Special:ListUsers/sysop|администраторов]], указав данный URL.",
"missingarticle-rev": "(версия № $1)",
"missingarticle-diff": "(разность: $1, $2)",
"perfcachedts": "Данные взяты из кэша; последний раз он обновлялся в $1. В кэше хранится не более {{PLURAL:$4|1=одной записи|$4 записи|$4 записей}}.",
"querypage-no-updates": "Обновление этой страницы сейчас отключено.\nПредставленные здесь данные не будут обновляться.",
"viewsource": "Просмотр кода",
- "viewsource-title": "Ð\9fÑ\80оÑ\81моÑ\82Ñ\80 иÑ\81Ñ\85одного Ñ\82екÑ\81Ñ\82а страницы $1",
+ "viewsource-title": "Ð\9fÑ\80оÑ\81моÑ\82Ñ\80 кода страницы $1",
"actionthrottled": "Ограничение по скорости",
- "actionthrottledtext": "Ð\94лÑ\8f боÑ\80Ñ\8cбÑ\8b Ñ\81о Ñ\81памом бÑ\8bло Ñ\83Ñ\81Ñ\82ановлено огÑ\80аниÑ\87ение на макÑ\81ималÑ\8cное Ñ\87иÑ\81ло попÑ\8bÑ\82ок вÑ\8bполнениÑ\8f Ñ\8dÑ\82ого дейÑ\81Ñ\82виÑ\8f в коÑ\80оÑ\82кий пÑ\80омежÑ\83Ñ\82ок вÑ\80емени â\80\94 и вÑ\8b иÑ\81Ñ\87еÑ\80пали Ñ\8dÑ\82оÑ\82 лимиÑ\82. Пожалуйста, повторите попытку через несколько минут.",
+ "actionthrottledtext": "Ð\92Ñ\8b иÑ\81Ñ\87еÑ\80пали Ñ\83Ñ\81Ñ\82ановленное длÑ\8f боÑ\80Ñ\8cбÑ\8b Ñ\81о Ñ\81памом огÑ\80аниÑ\87ение на макÑ\81ималÑ\8cное колиÑ\87еÑ\81Ñ\82во попÑ\8bÑ\82ок вÑ\8bполнениÑ\8f запÑ\80оÑ\88енного дейÑ\81Ñ\82виÑ\8f в коÑ\80оÑ\82кий пÑ\80омежÑ\83Ñ\82ок вÑ\80емени.\nПожалуйста, повторите попытку через несколько минут.",
"protectedpagetext": "Эта страница защищена для предотвращения её редактирования или совершений других действий.",
"viewsourcetext": "Вы можете просмотреть и скопировать исходный код этой страницы.",
"viewyourtext": "Вы можете просмотреть и скопировать исходный текст <strong>ваших правок</strong> на этой странице.",
"editinginterface": "<strong>Внимание:</strong> Вы редактируете страницу, содержащую текст интерфейса программного обеспечения.\nЕё изменение повлияет на внешний вид интерфейса для других пользователей этой вики.",
"translateinterface": "Чтобы добавить или изменить перевод этого сообщения, пожалуйста, используйте сайт локализации MediaWiki [https://translatewiki.net/ translatewiki.net].",
"cascadeprotected": "Данная страница защищена от изменений, поскольку она включена в {{PLURAL:$1|1=следующую страницу, для которой|следующие страницы, для которых}} включена каскадная защита:\n$2",
- "namespaceprotected": "У вас нет разрешения редактировать страницы в пространстве имён «$1».",
- "customcssprotected": "У вас нет разрешения редактировать эту CSS-страницу, так как она содержит личные настройки другого участника.",
- "customjsonprotected": "У вас нет разрешения редактировать эту JSON-страницу, так как она содержит личные настройки другого участника.",
- "customjsprotected": "У вас нет разрешения редактировать эту JavaScript-страницу, так как она содержит личные настройки другого участника.",
- "sitecssprotected": "У вас нет разрешения редактировать эту CSS-страницу, поскольку её изменение может повлиять на всех посетителей.",
- "sitejsonprotected": "У вас нет разрешения для редактирования этой JSON-страницы, поскольку её изменение может повлиять на всех посетителей.",
- "sitejsprotected": "У вас нет разрешения редактировать эту JavaScript страницу, поскольку её изменение может повлиять на всех посетителей.",
- "mycustomcssprotected": "У ваÑ\81 неÑ\82 пÑ\80ав длÑ\8f Ñ\80едакÑ\82иÑ\80ованиÑ\8f Ñ\8dÑ\82ого CSS страницы.",
- "mycustomjsonprotected": "У ваÑ\81 неÑ\82 пÑ\80ав длÑ\8f Ñ\80едакÑ\82иÑ\80ованиÑ\8f этой JSON-страницы.",
- "mycustomjsprotected": "У ваÑ\81 неÑ\82 пÑ\80ав длÑ\8f Ñ\80едакÑ\82иÑ\80ованиÑ\8f JavaScript на Ñ\81Ñ\82Ñ\80аниÑ\86е.",
- "myprivateinfoprotected": "У вас нет разрешения на изменение вашей личной информации",
- "mypreferencesprotected": "У ваÑ\81 неÑ\82 пÑ\80ав длÑ\8f Ñ\80едакÑ\82иÑ\80ованиÑ\8f настроек.",
- "ns-specialprotected": "СÑ\82Ñ\80аниÑ\86Ñ\8b пÑ\80оÑ\81Ñ\82Ñ\80анÑ\81Ñ\82ва имÑ\91н «{{ns:special}}» не могÑ\83Ñ\82 пÑ\80авиÑ\82Ñ\8cÑ\81Ñ\8f.",
+ "namespaceprotected": "У вас нет прав на редактирование страниц в пространстве имён «<strong>$1</strong>».",
+ "customcssprotected": "У вас нет прав на редактирование этой CSS-страницы, поскольку она содержит личные настройки другого участника.",
+ "customjsonprotected": "У вас нет прав на редактирование этой JSON-страницы, поскольку она содержит личные настройки другого участника.",
+ "customjsprotected": "У вас нет прав на редактирование этой JavaScript-страницы, поскольку она содержит личные настройки другого участника.",
+ "sitecssprotected": "У вас нет прав на редактирование этой CSS-страницы, поскольку её изменение может повлиять на всех посетителей.",
+ "sitejsonprotected": "У вас нет прав на редактирование этой JSON-страницы, поскольку её изменение может повлиять на всех посетителей.",
+ "sitejsprotected": "У вас нет прав на редактирование этой JavaScript страницу, поскольку её изменение может повлиять на всех посетителей.",
+ "mycustomcssprotected": "У ваÑ\81 неÑ\82 пÑ\80ав на Ñ\80едакÑ\82иÑ\80ование Ñ\8dÑ\82ой CSS страницы.",
+ "mycustomjsonprotected": "У ваÑ\81 неÑ\82 пÑ\80ав на Ñ\80едакÑ\82иÑ\80ование этой JSON-страницы.",
+ "mycustomjsprotected": "У ваÑ\81 неÑ\82 пÑ\80ав на Ñ\80едакÑ\82иÑ\80ование Ñ\8dÑ\82ой JavaScript-Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\8b.",
+ "myprivateinfoprotected": "У вас нет прав на изменение вашей личной информации",
+ "mypreferencesprotected": "У ваÑ\81 неÑ\82 пÑ\80ав на Ñ\80едакÑ\82иÑ\80ование настроек.",
+ "ns-specialprotected": "СÑ\82Ñ\80аниÑ\86Ñ\8b пÑ\80оÑ\81Ñ\82Ñ\80анÑ\81Ñ\82ва имÑ\91н «{{ns:special}}» не могÑ\83Ñ\82 бÑ\8bÑ\82Ñ\8c измененÑ\8b.",
"titleprotected": "Создание страницы с таким заголовком было запрещено участником [[User:$1|$1]].\nУказана следующая причина: <em>$2</em>.",
"filereadonlyerror": "Не удаётся изменить файл «$1», так как хранилище «$2» находится в режиме «только для чтения».\n\nСистемный администратор, заблокировавший базу, оставил следующее объяснение: «$3».",
"invalidtitle": "Недопустимое название",
"virus-scanfailed": "ошибка сканирования (код $1)",
"virus-unknownscanner": "неизвестный антивирус:",
"logouttext": "<strong>Вы завершили сеанс работы.</strong>\n\nНекоторые страницы могут продолжить отображаться так, как будто вы все ещё не завершили сеанс, пока вы не обновите кэш браузера.",
- "cannotlogoutnow-title": "Невозможно выйти прямо сейчас",
- "cannotlogoutnow-text": "Нельзя выйти во время использования $1.",
+ "cannotlogoutnow-title": "Ð\9dевозможно вÑ\8bйÑ\82и из Ñ\81иÑ\81Ñ\82емÑ\8b пÑ\80Ñ\8fмо Ñ\81ейÑ\87аÑ\81",
+ "cannotlogoutnow-text": "Ð\9dелÑ\8cзÑ\8f вÑ\8bйÑ\82и из Ñ\81иÑ\81Ñ\82емÑ\8b во вÑ\80емÑ\8f иÑ\81полÑ\8cзованиÑ\8f $1.",
"welcomeuser": "Добро пожаловать, $1!",
- "welcomecreation-msg": "Ваша учётная запись успешно создана.\nТеперь вы также можете провести [[Special:Preferences|персональную настройку]] сайта {{SITENAME}}.",
+ "welcomecreation-msg": "Ваша учётная запись была создана.\nТеперь вы также можете изменить [[Special:Preferences|персональные настройки]] для сайта {{SITENAME}}, если вы желаете.",
"yourname": "Имя учётной записи:",
"userlogin-yourname": "Имя учётной записи",
"userlogin-yourname-ph": "Введите имя вашей учётной записи",
"userlogin-signwithsecure": "Защищённое соединение",
"cannotlogin-title": "Невозможно войти",
"cannotlogin-text": "Вход в систему невозможен.",
- "cannotloginnow-title": "Невозможно войти прямо сейчас",
+ "cannotloginnow-title": "Ð\9dевозможно войÑ\82и в Ñ\81иÑ\81Ñ\82емÑ\83 пÑ\80Ñ\8fмо Ñ\81ейÑ\87аÑ\81",
"cannotloginnow-text": "Нельзя войти во время использования $1.",
"cannotcreateaccount-title": "Невозможно создать учётные записи",
"cannotcreateaccount-text": "Прямое создание учетных записей не включено в этой вики.",
"userlogout": "Завершение сеанса",
"notloggedin": "Вы не представились системе",
"userlogin-noaccount": "Нет учётной записи?",
- "userlogin-joinproject": "Присоединиться к проекту",
+ "userlogin-joinproject": "Присоединиться к проекту {{SITENAME}}.",
"createaccount": "Создать учётную запись",
"userlogin-resetpassword-link": "Сбросить ваш пароль?",
"userlogin-helplink2": "Помощь по входу",
"createacct-error": "Ошибка создания учётной записи",
"createaccounterror": "Невозможно создать учётную запись: $1",
"nocookiesnew": "Участник зарегистрирован, но не представлен. {{SITENAME}} использует «cookies» для представления участников. У вас «cookies» запрещены. Пожалуйста, разрешите их, а затем представьтесь со своиим новым именем участника и паролем.",
- "nocookieslogin": "{{SITENAME}} использует «cookies» для представления участников. Вы их отключили. Пожалуйста, включите их и попробуйте снова.",
+ "nocookieslogin": "{{SITENAME}} использует «cookies»-файлы для представления участников. Вы отключили использование «cookies»-файлов. Пожалуйста, включите использование «cookies»-файлов и попробуйте снова.",
"nocookiesfornew": "Учётная запись участника не была создана из-за невозможности проверить её источник. \nУбедитесь, что включены «cookies», обновите страницу и попробуйте ещё раз.",
"nocookiesforlogin": "{{int:nocookieslogin}}",
"createacct-loginerror": "Учётная запись была успешно создана, но вы не смогли войти в систему автоматически. Пожалуйста, [[Special:UserLogin|авторизуйтесь вручную]].",
"createacct-another-realname-tip": "Настоящее имя (необязательное поле).\nЕсли вы укажете его, то оно будет использовано для того, чтобы показать, кем была внесена правка страницы.",
"pt-login": "Войти",
"pt-login-button": "Войти",
- "pt-login-continue-button": "Продолжить процедуру входа",
+ "pt-login-continue-button": "Продолжить процедуру входа в систему",
"pt-createaccount": "Создать учётную запись",
"pt-userlogout": "Выйти",
"php-mail-error-unknown": "Неизвестная ошибка в PHP-функции mail()",
"permissionserrorstext": "У вас нет прав на выполнение этой операции по {{PLURAL:$1|1=следующей причине|следующим причинам}}:",
"permissionserrorstext-withaction": "У вас нет прав на выполнение действия «$2» по {{PLURAL:$1|1=следующей причине|следующим причинам}}:",
"contentmodelediterror": "Вы не можете редактировать эту версию, поскольку модель её содержания — <code>$1</code>, отличающаяся от текущей модели содержания страницы — <code>$2</code>.",
- "recreate-moveddeleted-warn": "'''Внимание. Вы пытаетесь воссоздать страницу, которая ранее удалялась.'''\n\nПроверьте, действительно ли вам нужно воссоздавать эту страницу.\nНиже приведены журналы удалений и переименований этой страницы.",
+ "recreate-moveddeleted-warn": "<strong>Внимание: Вы пытаетесь воссоздать страницу, которая ранее удалялась.</strong>\n\nПроверьте, действительно ли вам нужно воссоздавать эту страницу.\nНиже для справки приведены журналы удаления и переименований этой страницы.",
"moveddeleted-notice": "Эта страница была удалена.\nНиже для справки приведены журналы удаления, защиты и перемещения для этой страницы.",
"moveddeleted-notice-recent": "К сожалению, эта страница была недавно удалена (в течение последних 24 часов).\nНиже для справки приведены журналы удаления, защиты и перемещения для этой страницы.",
"log-fulllog": "Просмотреть журнал целиком",
"unstrip-size-warning": "Превышен лимит размера Unstrip ($1)",
"unstrip-size-category": "Страницы с превышенным лимитом размера Unstrip",
"converter-manual-rule-error": "Ошибка в ручном правиле преобразования языка",
- "undo-success": "Ð\9fÑ\80авка можеÑ\82 бÑ\8bÑ\82Ñ\8c оÑ\82менена. Ð\9fожалÑ\83йÑ\81Ñ\82а, пÑ\80оÑ\81моÑ\82Ñ\80иÑ\82е Ñ\81Ñ\80авнение веÑ\80Ñ\81ий, Ñ\87Ñ\82обÑ\8b Ñ\83бедиÑ\82Ñ\8cÑ\81Ñ\8f, Ñ\87Ñ\82о Ñ\8dÑ\82о именно Ñ\82е изменениÑ\8f, коÑ\82оÑ\80Ñ\8bе ваÑ\81 инÑ\82еÑ\80еÑ\81Ñ\83Ñ\8eÑ\82, и нажмиÑ\82е «Ð\97апиÑ\81аÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83», Ñ\87Ñ\82обÑ\8b изменениÑ\8f вÑ\81Ñ\82Ñ\83пили в Ñ\81илÑ\83.",
+ "undo-success": "Ð\9fÑ\80авка можеÑ\82 бÑ\8bÑ\82Ñ\8c оÑ\82менена. Ð\9fожалÑ\83йÑ\81Ñ\82а, пÑ\80оÑ\81моÑ\82Ñ\80иÑ\82е Ñ\81Ñ\80авнение веÑ\80Ñ\81ий, Ñ\87Ñ\82обÑ\8b Ñ\83бедиÑ\82Ñ\8cÑ\81Ñ\8f, Ñ\87Ñ\82о Ñ\8dÑ\82о именно Ñ\82е изменениÑ\8f, коÑ\82оÑ\80Ñ\8bе ваÑ\81 инÑ\82еÑ\80еÑ\81Ñ\83Ñ\8eÑ\82, и нажмиÑ\82е «Ð\97апиÑ\81аÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83», Ñ\87Ñ\82обÑ\8b ваÑ\88а оÑ\82мена пÑ\80авки бÑ\8bла Ñ\81оÑ\85Ñ\80анена.",
"undo-failure": "Правка не может быть отменена из-за несовместимости промежуточных изменений.",
"undo-main-slot-only": "Правка не может быть отменена, поскольку оно включает контент вне основного слота.",
"undo-norev": "Правка не может быть отменена, так как её не существует или она была удалена.",
"last": "пред.",
"page_first": "первая",
"page_last": "последняя",
- "histlegend": "Выбор версий: отметьте версии страницы, которые вы хотите сравнить, и нажмите '''{{int:compare-submit}}'''.<br />\nПояснения: '''({{int:cur}})''' — отличия от текущей версии; '''({{int:last}})''' — отличия от предшествующей версии; '''{{int:minoreditletter}}''' — незначительные изменения.",
+ "histlegend": "Выбор версий: отметьте версии страницы, которые вы хотите сравнить, и нажмите <strong>{{int:compare-submit}}</strong>.<br />\nПояснения: <strong>({{int:cur}})</strong> — отличия от текущей версии; <strong>({{int:last}})</strong> — отличия от предшествующей версии; <strong>{{int:minoreditletter}}</strong> — незначительные изменения.",
"history-fieldset-title": "Поиск правок",
"history-show-deleted": "Только удалённые правки",
"histfirst": "старейшие",
"history-feed-item-nocomment": "$1 в $2",
"history-feed-empty": "Запрашиваемой страницы не существует.\nОна могла быть удалена или переименована.\nПопробуйте [[Special:Search|найти в вики]] похожие страницы.",
"history-edit-tags": "Изменить теги выбранных версий",
- "rev-deleted-comment": "(опиÑ\81ание пÑ\80авки Ñ\83далено)",
+ "rev-deleted-comment": "(опиÑ\81ание пÑ\80авки Ñ\81Ñ\82Ñ\91Ñ\80Ñ\82о)",
"rev-deleted-user": "(имя автора стёрто)",
- "rev-deleted-event": "(деÑ\82али жÑ\83Ñ\80нала Ñ\83далены)",
+ "rev-deleted-event": "(подÑ\80обноÑ\81Ñ\82и Ñ\81Ñ\82Ñ\91Ñ\80Ñ\82ы)",
"rev-deleted-user-contribs": "[имя участника или IP-адрес удалены — правка скрыта со страницы вклада]",
"rev-deleted-text-permission": "Эта версия страницы была '''удалена'''.\nПодробности приведены в [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале удалений].",
"rev-suppressed-text-permission": "Эта версия страницы была <strong>скрыта</strong>.\nПодробности приведены в [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} журнале сокрытий].",
"rev-suppressed-diff-view": "Одна из версий этого сравнения версий была <strong>скрыта</strong>.\nВы можете просмотреть это сравнение. Подробности приведены в [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} журнале сокрытий].",
"rev-delundel": "показать/скрыть",
"rev-showdeleted": "показать",
- "revisiondelete": "Удалить / восстановить версии страницы",
+ "revisiondelete": "Удалить/восстановить версии страницы",
"revdelete-nooldid-title": "Не задана целевая версия",
- "revdelete-nooldid-text": "Ð\92Ñ\8b не задали Ñ\86елевÑ\83Ñ\8e веÑ\80Ñ\81иÑ\8e (веÑ\80Ñ\81ии) длÑ\8f вÑ\8bполнениÑ\8f Ñ\8dÑ\82ой Ñ\84Ñ\83нкÑ\86ии, Ñ\83казаннаÑ\8f веÑ\80Ñ\81иÑ\8f не Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82, или вы пытаетесь скрыть текущую версию.",
+ "revdelete-nooldid-text": "ЦелеваÑ\8f веÑ\80Ñ\81иÑ\8f не заданÑ\8b, Ñ\83казаннаÑ\8f веÑ\80Ñ\81иÑ\8f не Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82 или же вы пытаетесь скрыть текущую версию.",
"revdelete-no-file": "Указанный файл не существует.",
"revdelete-show-file-confirm": "Вы уверены, что вы хотите просмотреть удалённую версию файла «<nowiki>$1</nowiki>» от $2, $3?",
"revdelete-show-file-submit": "Да",
"revdelete-radio-same": "(не изменять)",
"revdelete-radio-set": "Скрытая",
"revdelete-radio-unset": "Видимая",
- "revdelete-suppress": "Скрывать данные также и от администраторов",
- "revdelete-unsuppress": "Снять ограничения с восстановленных версий",
+ "revdelete-suppress": "Скрыть данные также и от администраторов",
+ "revdelete-unsuppress": "Снять ограничения видимости с восстановленных версий",
"revdelete-log": "Причина:",
"revdelete-submit": "Применить к {{PLURAL:$1|1=выбранной версии|выбранным версиям}}",
"revdelete-success": "Видимость версии обновлена.",
"revdelete-concurrent-change": "Ошибка изменения записи от $2, $1: её статус был изменён кем-то другим, пока вы пытались изменить его.\nПожалуйста, проверьте журналы.",
"revdelete-only-restricted": "Ошибка сокрытия записи от $2 $1: вы не можете скрыть запись от просмотра администраторами без выбора одной из других настроек сокрытия.",
"revdelete-reason-dropdown": "* Стандартные причины удаления\n** Нарушение авторских прав\n** Неуместные личные сведения\n** Неуместное имя участника\n** Потенциально клеветнические сведения",
- "revdelete-otherreason": "Другая/дополнительная причина:",
+ "revdelete-otherreason": "Другая причина/дополнение:",
"revdelete-reasonotherlist": "Другая причина",
"revdelete-edit-reasonlist": "Редактировать список причин",
"revdelete-offender": "Автор версии страницы:",
"default": "по умолчанию",
"prefs-files": "Файлы",
"prefs-custom-css": "Собственный CSS",
- "prefs-custom-json": "Ð\9fолÑ\8cзоваÑ\82елÑ\8cÑ\81кий JSON",
+ "prefs-custom-json": "СобÑ\81Ñ\82веннÑ\8bй JSON",
"prefs-custom-js": "Собственный JS",
"prefs-common-config": "Общие CSS/JSON/JavaScript для всех тем оформления:",
"prefs-reset-intro": "Эта страница может быть использована для сброса ваших настроек на стандартные.\nУчтите, что это действие невозможно отменить.",
"userrights-groupsmember": "Состоит в группах:",
"userrights-groupsmember-auto": "Неявно состоит в группах:",
"userrights-groupsmember-type": "$1",
- "userrights-groups-help": "Вы можете изменить группы, в которые входит {{GENDER:$1|этот участник|эта участница}}.\n* Если около названия группы стоит отметка — {{GENDER:$1|участник|участница}} входит в эту группу.\n* Если отметка не стоит — {{GENDER:$1|участник|участница}} не входит в эту группу.\n* Символ * указывает на то, что вы не сможете удалить {{GENDER:$1|участника|участницу}} из группы, если добавите {{GENDER:$1|его|её}} в неё (или наоборот).\n* Символ # указывает на то, что вы можете только отложить время истечения членства в этой группы, вы не можете перенести его на более ранний срок.",
+ "userrights-groups-help": "Вы можете изменить группы, в которые входит {{GENDER:$1|этот участник|эта участница}}.\n* Если около названия группы стоит отметка — {{GENDER:$1|участник|участница}} входит в эту группу.\n* Если отметка не стоит — {{GENDER:$1|участник|участница}} не входит в эту группу.\n* Символ * указывает на то, что вы не сможете удалить {{GENDER:$1|участника|участницу}} из группы, если добавите {{GENDER:$1|его|её}} в неё (или наоборот).\n* Символ # указывает на то, что вы можете только отложить, но не перенести время истечения членства в этой группе на более ранний срок.",
"userrights-reason": "Причина:",
"userrights-no-interwiki": "У вас нет разрешения изменять права участников в других вики.",
"userrights-nodatabase": "База данных $1 не существует или расположена не локально.",
"block": "Блокировка участника",
"unblock": "Разблокировка участника",
"blockip": "Заблокировать {{GENDER:$1|участника|участницу}}",
- "blockiptext": "Используйте форму ниже, чтобы заблокировать возможность записи с определённого IP-адреса или имени участника.\nЭто может быть сделано только для предотвращения вандализма и только в соответствии с [[{{MediaWiki:Policy-url}}|правилами]].\nНиже укажите конкретную причину (к примеру, процитируйте некоторые страницы с признаками вандализма).\nВы можете заблокировать диапазоны IP-адресов, используя [https://ru.wikipedia.org/wiki/Бесклассовая_адресация CIDR]-синтаксис. Максимально допустимый диапазон — /$1 для протокола IPv4 и /$2 для протокола IPv6.",
+ "blockiptext": "Используйте форму ниже, чтобы заблокировать возможность редактирования с определённого IP-адреса или имени участника.\nЭтот инструмент следует использовать для предотвращения вандализма и только в соответствии с [[{{MediaWiki:Policy-url}}|правилами]].\nНиже укажите конкретную причину (к примеру, процитируйте некоторые страницы с признаками вандализма).\nВы можете заблокировать диапазоны IP-адресов, используя [https://ru.wikipedia.org/wiki/Бесклассовая_адресация CIDR]-синтаксис. Максимально допустимый диапазон — /$1 для протокола IPv4 и /$2 для протокола IPv6.",
"ipaddressorusername": "IP-адрес или имя участника:",
"ipbexpiry": "Закончится через:",
"ipbreason": "Причина:",
"ipboptions": "2 часа:2 hours,1 день:1 day,3 дня:3 days,1 неделя:1 week,2 недели:2 weeks,1 месяц:1 month,3 месяца:3 months,6 месяцев:6 months,1 год:1 year,бессрочно:infinite",
"ipbhidename": "Скрыть имя участника из правок и списков",
"ipbwatchuser": "Добавить в список наблюдения личную страницу участника и его страницу обсуждения",
- "ipb-disableusertalk": "Запретить этому участнику редактировать свою страницу обсуждения во время блокировки",
+ "ipb-disableusertalk": "Запретить этому участнику редактировать свою страницу обсуждения",
"ipb-change-block": "Переблокировать участника с этими настройками",
"ipb-confirm": "Подтвердить блокировку",
"badipaddress": "IP-адрес записан в неправильном формате, или участника с таким именем не существует.",
"autoblocklist-submit": "Найти",
"autoblocklist-legend": "Список автоблокировок",
"autoblocklist-localblocks": "{{PLURAL:$1|Локальная автоблокировка|Локальные автоблокировки}}",
- "autoblocklist-total-autoblocks": "Ð\92Ñ\81его авÑ\82облоков: $1",
+ "autoblocklist-total-autoblocks": "Ð\92Ñ\81его авÑ\82облокиÑ\80овок: $1",
"autoblocklist-empty": "Список автоблокировок пуст.",
"autoblocklist-otherblocks": "{{PLURAL:$1|Другая автоблокировка|Другие автоблокировки}}",
"ipblocklist": "Заблокированные участники",
"moveuserpage-warning": "<strong>Внимание:</strong> вы собираетесь переименовать страницу участника. Пожалуйста, обратите внимание, что переименована будет только страница, участник <strong>не</strong> будет переименован.",
"movecategorypage-warning": "<strong>Предупреждение:</strong> Вы собираетесь переименовать страницу категории. Пожалуйста, обратите внимание, что будет переименована только эта страница, а все страницы старой категории <em>не</em> будут перекатегоризованы в новую.",
"movenologintext": "Вы должны [[Special:UserLogin|представиться системе]],\nчтобы иметь возможность переименовать страницы.",
- "movenotallowed": "У вас нет разрешения переименовывать страницы.",
- "movenotallowedfile": "У вас нет разрешения переименовывать файлы.",
- "cant-move-user-page": "У вас нет разрешения переименовывать основные страницы участников.",
+ "movenotallowed": "У вас нет прав на переименовывание страниц.",
+ "movenotallowedfile": "У вас нет прав на переименовывание файлов.",
+ "cant-move-user-page": "У вас нет прав на переименовывание основных страниц участников.",
"cant-move-to-user-page": "У вас нет прав переименовывать страницу в страницу участника (можно переименовать в подстраницу).",
- "cant-move-category-page": "У вас нет разрешения переименовывать страницы категорий.",
- "cant-move-to-category-page": "У вас нет разрешения переименовывать страницы в страницу категории.",
- "cant-move-subpages": "У вас нет разрешения переименовывать подстраницы.",
+ "cant-move-category-page": "У вас нет прав на переименовывание страниц категорий.",
+ "cant-move-to-category-page": "У вас нет прав на переименовывание страницы в страницу категории.",
+ "cant-move-subpages": "У вас нет прав на переименовывание подстраниц.",
"namespace-nosubpages": "Пространство имён «$1» не разрешает создание страниц.",
"newtitle": "Новое название:",
"move-watch": "Добавить в список наблюдения исходную и целевую страницы",
"movenosubpage": "У этой страницы нет подстраниц.",
"movereason": "Причина:",
"revertmove": "возврат",
- "delete_and_move_text": "ЦелеваÑ\8f Ñ\81траница с именем «[[:$1]]» уже существует. \nХотите удалить её, чтобы сделать возможным переименование?",
+ "delete_and_move_text": "Страница с именем «[[:$1]]» уже существует. \nХотите удалить её, чтобы сделать возможным переименование?",
"delete_and_move_confirm": "Да, удалить эту страницу",
"delete_and_move_reason": "Удалено для возможности переименования «[[$1]]»",
"selfmove": "Невозможно переименовать страницу: исходное и новое имя страницы совпадают.",
"immobile-target-namespace-iw": "Ссылка интервики не может быть использована для переименования.",
"immobile-source-page": "Эту страницу нельзя переименовать.",
"immobile-target-page": "Нельзя присвоить странице это имя.",
- "bad-target-model": "Невозможно преобразовать $1 в $2: несовместимые модели данных.",
+ "bad-target-model": "Невозможно преобразовать $1 в $2. У страниц несовместимые модели содержимого.",
"imagenocrossnamespace": "Невозможно дать файлу имя из другого пространства имён",
- "nonfile-cannot-move-to-file": "Невозможно переименовывать страницы в файлы",
+ "nonfile-cannot-move-to-file": "Невозможно переименовывать не-файловые страницы в файлы",
"imagetypemismatch": "Новое расширение файла не соответствует его типу",
"imageinvalidfilename": "Целевое имя файла ошибочно",
"fix-double-redirects": "Исправить перенаправления, указывающие на прежнее название",
"anonymous": "{{PLURAL:$1|1=Анонимный участник|Анонимные участники}} {{grammar:genitive|{{SITENAME}}}}",
"siteuser": "{{GENDER:$2|участник|участница}} {{grammar:genitive|{{SITENAME}}}} $1",
"anonuser": "анонимный участник {{grammar:genitive|{{SITENAME}}}} $1",
- "lastmodifiedatby": "ÐÑ\82а Ñ\81Ñ\82Ñ\80аниÑ\86а поÑ\81ледний Ñ\80аз бÑ\8bла оÑ\82Ñ\80едакÑ\82иÑ\80ована $1 в $2, авÑ\82оÑ\80 изменениÑ\8f â\80\94 $3.",
+ "lastmodifiedatby": "Ð\92 поÑ\81ледний Ñ\80аз Ñ\8dÑ\82а Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\80едакÑ\82иÑ\80овалаÑ\81Ñ\8c $1, в $2 авÑ\82оÑ\80ом $3.",
"othercontribs": "В создании приняли участие: $1.",
"others": "другие",
"siteusers": "{{PLURAL:$2|1={{GENDER:$1|участник|участница}}|участники}} {{grammar:genitive|{{SITENAME}}}} $1",
"filedelete-archive-read-only": "Архивная директория «$1» не доступна для записи веб-серверу.",
"previousdiff": "← Предыдущая правка",
"nextdiff": "Следующая правка →",
- "mediawarning": "'''Внимание'''. Этот тип файла может содержать вредоносный программный код.\nПри его запуске ваша система может быть заражена.",
+ "mediawarning": "<strong>Внимание</strong>. Этот тип файла может содержать вредоносный программный код.\nПри его запуске ваша система может быть заражена.",
"imagemaxsize": "Ограничение на размер изображения:<br />''(для страницы описания файла)''",
"thumbsize": "Размер уменьшенной версии изображения:",
"widthheight": "$1 × $2",
"confirm-unwatch-top": "Удалить эту страницу из вашего списка наблюдения?",
"confirm-rollback-button": "ОК",
"confirm-rollback-top": "Откатить правки на этой странице?",
+ "confirm-mcrundo-title": "Отменить изменение",
+ "mcrundofailed": "Отменить не удалось",
+ "mcrundo-missingparam": "Отсутствуют обязательные параметры по запросу.",
+ "mcrundo-changed": "Эта страница была изменена с тех пор, как вы просмотрели различия. Пожалуйста, проверьте новое изменение.",
"semicolon-separator": "; ",
"comma-separator": ", ",
"colon-separator": ": ",
"edit-error-long": "Ошибки:\n\n$1",
"revid": "версия $1",
"pageid": "ID страницы $1",
- "interfaceadmin-info": "$1\n\nПрава на редактирование общесайтных CSS/JS/JSON были недавно вынесены из права <code>editinterface</code>. Если вы не понимаете, почему вы наткнулись на эту ошибку, см. [[mw:MediaWiki_1.32/interface-admin]].",
+ "interfaceadmin-info": "$1\n\nПрава на редактирование общесайтных CSS/JS/JSON-файлов были недавно вынесены из права <code>editinterface</code>. Если вы не понимаете, почему вы наткнулись на эту ошибку, см. [[mw:MediaWiki_1.32/interface-admin]].",
"rawhtml-notallowed": "<html> теги могут быть использованы только в пределах обычных страниц.",
"gotointerwiki": "Покидаем {{grammar:accusative|{{SITENAME}}}}...",
"gotointerwiki-invalid": "Указан некорректный заголовок.",
"cascadeprotected": "Сторінка є замнкута, бо є вложена до {{PLURAL:$1|наслїдуючой сторінкы замкнуты|наслїдуючіх сторінок замнкнутых|наслїдуючіх сторінок замнкнутых}} каскадовым замком:\n$2",
"namespaceprotected": "Не маєте права едітовати сторінкы в просторї назв «$1».",
"customcssprotected": "Не маєте права едітовати тоту сторінку з CSS, бо обсягує персоналны наставлїна іншого хоснователя.",
+ "customjsonprotected": "Не маєте права едітовати тоту сторінку з JSON, бо обсягує персоналны наставлїна іншого хоснователя.",
"customjsprotected": "Не маєте права едітовати тоту сторінку з JavaScript-ом, бо обсягує персоналны наставлїна іншого хоснователя.",
"mycustomcssprotected": "Не мате права на управы той CSS сторінкы.",
+ "mycustomjsonprotected": "Не мате права на едітованя той JSON сторінкы.",
"mycustomjsprotected": "Не мате права на едітованя той JavaScript сторінкы.",
"myprivateinfoprotected": "Не мате дозволїня мінити свої пріватны інформації.",
"mypreferencesprotected": "Не мате дозволїня мінити свої наставлїня.",
"cascadeprotected": "Táto stránka bola zamknutá proti úpravám, pretože je použitá na {{PLURAL:$1|nasledovnej stránke, ktorá je zamknutá|nasledovných stránkach, ktoré sú zamknuté}} voľbou „kaskádového zamknutia“:\n$2",
"namespaceprotected": "Nemáte povolenie upravovať stránky v mennom priestore '''$1'''.",
"customcssprotected": "Nemáte právo upravovať túto CSS stránku, pretože obsahuje osobné nastavenie iného používateľa.",
+ "customjsonprotected": "Nemáte právo upravovať túto JSON stránku, pretože obsahuje osobné nastavenie iného používateľa.",
"customjsprotected": "Nemáte právo upravovať túto JavaScript stránku, pretože obsahuje osobné nastavenie iného používateľa.",
"mycustomcssprotected": "Nemáte povolenie na úpravu tejto CSS stránky.",
+ "mycustomjsonprotected": "Nemáte povolenie na úpravu tejto JSON stránky.",
"mycustomjsprotected": "Nemáte povolenie na úpravu tejto JavaScriptovej stránky.",
"myprivateinfoprotected": "Nemáte povolenie na úpravu vašich súkromných informácií.",
"mypreferencesprotected": "Nemáte povolenie na úpravu vašich nastavení.",
"edit-hook-aborted": "Urejanje je bilo brez obrazložitve prekinjeno zaradi neznane napake.",
"edit-gone-missing": "Strani ni mogoče posodobiti.\nIzgleda, da je bila izbrisana.",
"edit-conflict": "Navzkrižje urejanj.",
- "edit-no-change": "Vaše urejanje je bilo prezrto, saj ni vsebovalo sprememb.",
+ "edit-no-change": "Tvoje urejanje je bilo prezrto, saj ni vsebovalo sprememb.",
"postedit-confirmation-created": "Stran je bila ustvarjena.",
"postedit-confirmation-restored": "Stran je bila obnovljena.",
- "postedit-confirmation-saved": "Vaše urejanje smo shranili.",
- "postedit-confirmation-published": "Vaše urejanje smo objavili.",
+ "postedit-confirmation-saved": "Tvoje urejanje je bilo shranjeno.",
+ "postedit-confirmation-published": "Tvoje urejanje smo objavili.",
"edit-already-exists": "Ni bilo mogoče ustvariti nove strani, ker že obstaja.",
"defaultmessagetext": "Prednastavljeno besedilo",
"content-failed-to-parse": "Nisem mogel razčleniti vsebine $2 za obliko $1: $3",
"confirm-unwatch-top": "Odstranim stran z vašega spiska nadzorov?",
"confirm-rollback-button": "V redu",
"confirm-rollback-top": "Povrnemo urejanja te strani?",
+ "confirm-mcrundo-title": "Razveljavi spremembo",
+ "mcrundofailed": "Razveljavitev ni uspela",
+ "mcrundo-missingparam": "Pri zahtevi manjkajo zahtevani parametri.",
+ "mcrundo-changed": "Stran je bila spremenjena, odkar ste si ogledali primerjavo. Prosimo, preglejte nove spremembe.",
"percent": "$1 %",
"quotation-marks": "»$1«",
"imgmultipageprev": "← prejšnja stran",
"tog-watchlisthideminor": "Сакриј мање измене са списка надгледања",
"tog-watchlisthideliu": "Сакриј измене пријављених корисника са списка надгледања",
"tog-watchlistreloadautomatically": "Аутоматски освежи списак надгледања кад год се филтер промени (потребан JavaScript)",
- "tog-watchlistunwatchlinks": "Ð\94одаÑ\98 везе за диÑ\80екÑ\82но додаваÑ\9aе/Ñ\83клаÑ\9aаÑ\9aе Ñ\81Ñ\82авки Ñ\81а Ñ\81пиÑ\81ка надгледаÑ\9aа (поÑ\82Ñ\80ебан JavaScript)",
+ "tog-watchlistunwatchlinks": "Ð\94одаÑ\98 ознаÑ\87иваÑ\87е за пÑ\80екид надгледаÑ\9aа/нагледаÑ\9aе ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) на надгледане Ñ\81Ñ\82Ñ\80аниÑ\86е Ñ\81а пÑ\80оменама (Ð\88аваÑ\81кÑ\80ипÑ\82 Ñ\98е неопÑ\85одан за Ñ\84Ñ\83нкÑ\86ионалноÑ\81Ñ\82 пÑ\80ебаÑ\86иваÑ\9aа)",
"tog-watchlisthideanons": "Сакриј измене анонимних корисника са списка надгледања",
"tog-watchlisthidepatrolled": "Сакриј патролиране измене са списка надгледања",
"tog-watchlisthidecategorization": "Сакриј категоризацију страница",
"views": "Прегледи",
"toolbox": "Алатке",
"tool-link-userrights": "Промени {{GENDER:$1|корисничке}} групе",
- "tool-link-userrights-readonly": "Ð\9fÑ\80еглед {{GENDER:$1|корисничких}} група",
+ "tool-link-userrights-readonly": "Ð\9fÑ\80иказ {{GENDER:$1|корисничких}} група",
"tool-link-emailuser": "Слање имејла {{GENDER:$1|кориснику|корисници}}",
"imagepage": "Погледај страницу датотеке",
"mediawikipage": "Погледај страницу поруке",
"missingarticle-diff": "(разлика: $1, $2)",
"readonly_lag": "База података је аутоматски закључана да би се секундарни сервери базе података ускладили с главним.",
"internalerror": "Унутрашња грешка",
- "internalerror_info": "Ð\98нÑ\82еÑ\80на грешка: $1",
+ "internalerror_info": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aа грешка: $1",
"internalerror-fatal-exception": "Грешка необрађеног изузетка типа „$1“",
"filecopyerror": "Не могу да копирам датотеку „$1“ у „$2“.",
"filerenameerror": "Не могу да преименујем датотеку „$1“ у „$2“.",
"cannotloginnow-title": "Пријава тренутно није могућа",
"cannotloginnow-text": "Пријава није могућа када се користи $1.",
"cannotcreateaccount-title": "Не могу да отворим налоге",
- "cannotcreateaccount-text": "Ð\94иÑ\80екÑ\82но пÑ\80авÑ\99ење налога није омогућено на овом викију.",
+ "cannotcreateaccount-text": "Ð\94иÑ\80екÑ\82но оÑ\82ваÑ\80ање налога није омогућено на овом викију.",
"yourdomainname": "Домен:",
"password-change-forbidden": "Не можете да промените лозинку на овом викију.",
"externaldberror": "Дошло је до грешке при потврди идентитета базе података или вам није дозвољено да ажурирате свој спољни налог.",
"botpasswords-no-provider": "BotPasswordsSessionProvider није доступан.",
"botpasswords-restriction-failed": "Не можете се пријавити због ограничења лозинки за ботове.",
"botpasswords-not-exist": "Корисник „$1“ нема лозинку бота „$2“.",
- "resetpass_forbidden": "Ð\9bозинка не може биÑ\82и пÑ\80омеÑ\9aена",
- "resetpass_forbidden-reason": "Ð\9bозинке ниÑ\98е могÑ\83Ñ\9bе пÑ\80омениÑ\82и: $1",
+ "resetpass_forbidden": "Ð\9dе могÑ\83 да пÑ\80оменим лозинке",
+ "resetpass_forbidden-reason": "Ð\9dе могÑ\83 да пÑ\80оменим лозинке: $1",
"resetpass-no-info": "Морате бити пријављени да бисте приступили овој страници.",
"resetpass-submit-loggedin": "Промени лозинку",
"resetpass-submit-cancel": "Откажи",
"diff-multi-sameuser": "({{PLURAL:$1|Једна међуревизија истог корисника није приказана|$1 међуревизија истог корисника нису приказане|$1 међуревизија истог корисника није приказано}})",
"diff-multi-otherusers": "({{PLURAL:$1|Једна међуревизија|$1 међуревизије|$1 међуревизија}} од стране {{PLURAL:$2|још једног корисника није приказана|$2 корисника није приказано}})",
"diff-multi-manyusers": "({{PLURAL:$1|Није приказана међуизмена|Нису приказане $1 међуизмене|Није приказано $1 међуизмена}} од више од $2 корисника)",
- "diff-paragraph-moved-tonew": "Пасус је премештен. Кликните да пређете на његово ново место.",
- "diff-paragraph-moved-toold": "Ð\9fаÑ\81Ñ\83Ñ\81 Ñ\98е пÑ\80емеÑ\88Ñ\82ен. Ð\9aликниÑ\82е да пÑ\80еÑ\92еÑ\82е на Ñ\9aегово Ñ\81Ñ\82аÑ\80о меÑ\81Ñ\82о.",
+ "diff-paragraph-moved-tonew": "Пасус је премештен. Кликните да пређете на нову локацију.",
+ "diff-paragraph-moved-toold": "Ð\9fаÑ\81Ñ\83Ñ\81 Ñ\98е пÑ\80емеÑ\88Ñ\82ен. Ð\9aликниÑ\82е да пÑ\80еÑ\92еÑ\82е на Ñ\81Ñ\82аÑ\80Ñ\83 локаÑ\86иÑ\98Ñ\83.",
"difference-missing-revision": "{{PLURAL:$2|Једна ревизија|$2 ревизије}} ове разлике ($1) не {{PLURAL:$2|постоји|постоје}}.\n\nОво се обично дешава када пратите застарели линк до странице која је избрисана.\nДетаље можете да пронађете у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} евиденцији брисања].",
"searchresults": "Резултати претраге",
"search-filter-title-prefix-reset": "Претражи све странице",
"prefs-watchlist-edits-max": "Највећи број: 1000",
"prefs-watchlist-token": "Токен списка надгледања:",
"prefs-watchlist-managetokens": "Управљај жетонима",
- "prefs-misc": "Ð\94Ñ\80Ñ\83га подеÑ\88аваÑ\9aа",
+ "prefs-misc": "Разно",
"prefs-resetpass": "промени лозинку",
"prefs-changeemail": "промени или уклони имејл-адресу",
"prefs-setemail": "постави имејл-адресу",
"prefs-files": "Датотеке",
"prefs-custom-css": "прилагођени CSS",
"prefs-custom-json": "Прилагођени JSON",
- "prefs-custom-js": "прилагођени јаваскрипт",
+ "prefs-custom-js": "прилагођени JavaScript",
"prefs-common-config": "Дељени CSS/JSON/јаваскрипт за све теме:",
"prefs-reset-intro": "Можете користити ову страницу да поново поставите своја подешавања на подразумеване вредности сајта.\nОво се не може опозвати.",
"prefs-emailconfirm-label": "Потврда имејла:",
"editusergroup": "Учитај корисничке групе",
"editinguser": "Мењате корисничка права {{GENDER:$1|корисника|кориснице}} <strong>[[User:$1|$1]]</strong> $2",
"viewinguserrights": "Корисничка права {{GENDER:$1|корисника|кориснице}} <strong>[[User:$1|$1]]</strong> $2",
- "userrights-editusergroup": "Ð\9fÑ\80омена {{GENDER:$1|корисничких}} група",
- "userrights-viewusergroup": "Ð\9fÑ\80еглед {{GENDER:$1|корисничких}} група",
+ "userrights-editusergroup": "УÑ\80еÑ\92иваÑ\9aе {{GENDER:$1|корисничких}} група",
+ "userrights-viewusergroup": "Ð\9fÑ\80иказ {{GENDER:$1|корисничких}} група",
"saveusergroups": "Сачувај {{GENDER:$1|корисничке}} групе",
"userrights-groupsmember": "Члан група:",
"userrights-groupsmember-auto": "{{GENDER:$2|Имплицитан члан|Имплицитна чланица}} група:",
"grouppage-sysop": "{{ns:project}}:Администратори",
"grouppage-interface-admin": "{{ns:project}}:Администратори интерфејса",
"grouppage-bureaucrat": "{{ns:project}}:Бирократе",
- "grouppage-suppress": "{{ns:project}}:РевизоÑ\80",
+ "grouppage-suppress": "{{ns:project}}:Ð\91Ñ\80иÑ\81аÑ\87и измена",
"right-read": "читање страница",
"right-edit": "уређивање страница",
"right-createpage": "прављење страница (изузев страница за разговор)",
"right-block": "блокирање даљих измена других корисника",
"right-blockemail": "блокирање корисника да шаљу имејл",
"right-hideuser": "блокирање корисничког имена и његово сакривање од јавности",
- "right-ipblock-exempt": "заобилажење IP блокада, самоблокада и блокада опсега",
+ "right-ipblock-exempt": "заобилажење IP блокада, аутоблокада и блокада опсега",
"right-unblockself": "деблокирање самог себе",
"right-protect": "мењање нивоа заштите и уређивање страница под преносивом заштитом",
"right-editprotected": "уређивање страница под заштитом „{{int:protect-level-sysop}}“",
"grant-createaccount": "Отварање налога",
"grant-createeditmovepage": "Прављење, уређивање и премештање страница",
"grant-delete": "Брисање страница, ревизија и уноса у евиденцијама",
- "grant-editinterface": "УÑ\80еÑ\92иваÑ\9aе Ð\9cедиÑ\98авики именÑ\81ког пÑ\80оÑ\81Ñ\82оÑ\80а и коÑ\80иÑ\81ниÑ\87киÑ\85 CSS/JSON/Ð\88аваÑ\81кÑ\80ипÑ\82 Ñ\81Ñ\82Ñ\80аниÑ\86а",
+ "grant-editinterface": "УÑ\80еÑ\92иваÑ\9aе именÑ\81ког пÑ\80оÑ\81Ñ\82оÑ\80а Ð\9cедиÑ\98авики и JSON-а Ñ\81аÑ\98Ñ\82а/коÑ\80иÑ\81ника",
"grant-editmycssjs": "Уређивање вашег CSS/JSON/Јаваскрипта",
"grant-editmyoptions": "Уређивање ваших корисничких подешавања",
"grant-editmywatchlist": "Уређивање вашег списка надгледања",
"rcfilters-highlighted-filters-list": "Истакнуто: $1",
"rcfilters-quickfilters": "Сачувани филтери",
"rcfilters-quickfilters-placeholder-title": "Још нема сачуваних филтера",
- "rcfilters-quickfilters-placeholder-description": "Ð\94а биÑ\81Ñ\82е Ñ\81аÑ\87Ñ\83вали Ñ\81воÑ\98а подеÑ\88аваÑ\9aа Ñ\84илÑ\82еÑ\80а и поново иÑ\85 Ñ\83поÑ\82Ñ\80ебÑ\99авали каÑ\81ниÑ\98е, кликниÑ\82е на иконÑ\83 за ознакÑ\83 у подручју активних филтера — испод.",
+ "rcfilters-quickfilters-placeholder-description": "Ð\94а биÑ\81Ñ\82е Ñ\81аÑ\87Ñ\83вали Ñ\81воÑ\98а подеÑ\88аваÑ\9aа Ñ\84илÑ\82еÑ\80а и поново иÑ\85 Ñ\83поÑ\82Ñ\80ебÑ\99авали каÑ\81ниÑ\98е, кликниÑ\82е на иконÑ\83 за обележаваÑ\9aе у подручју активних филтера — испод.",
"rcfilters-savedqueries-defaultlabel": "Сачувани филтери",
"rcfilters-savedqueries-rename": "Преименуј",
"rcfilters-savedqueries-setdefault": "Постави као подразумевано",
"rcfilters-filter-editsbyself-label": "Ваше промене",
"rcfilters-filter-editsbyself-description": "Ваши сопствени доприноси.",
"rcfilters-filter-editsbyother-label": "Промене других",
- "rcfilters-filter-editsbyother-description": "Све измене осим ваших.",
+ "rcfilters-filter-editsbyother-description": "Све пÑ\80омене осим ваших.",
"rcfilters-filtergroup-userExpLevel": "Корисничка регистрација и искуство",
"rcfilters-filter-user-experience-level-registered-label": "Регистровани",
"rcfilters-filter-user-experience-level-registered-description": "Пријављени уредници.",
"rcfilters-filtergroup-watchlistactivity": "Стање на списку надгледања",
"rcfilters-filter-watchlistactivity-unseen-label": "Непогледане промене",
"rcfilters-filter-watchlistactivity-unseen-description": "Промене на страницама које нисте посетили од када су промене направљене.",
- "rcfilters-filter-watchlistactivity-seen-label": "Ð\9fогледане измене",
+ "rcfilters-filter-watchlistactivity-seen-label": "Ð\9fогледане пÑ\80омене",
"rcfilters-filter-watchlistactivity-seen-description": "Промене на страницама које сте посетили од када су промене направљене.",
"rcfilters-filtergroup-changetype": "Тип промене",
"rcfilters-filter-pageedits-label": "Измене страница",
"rcfilters-filter-categorization-label": "Промене категорија",
"rcfilters-filter-categorization-description": "Записи о страницама додатим или уклоњеним из категорија.",
"rcfilters-filter-logactions-label": "Евидентиране радње",
- "rcfilters-filter-logactions-description": "Ð\90дминиÑ\81Ñ\82Ñ\80аÑ\82ивне Ñ\80адÑ\9aе, пÑ\80авÑ\99ење налога, брисање страница, отпремања…",
+ "rcfilters-filter-logactions-description": "Ð\90дминиÑ\81Ñ\82Ñ\80аÑ\82ивне Ñ\80адÑ\9aе, оÑ\82ваÑ\80ање налога, брисање страница, отпремања…",
"rcfilters-hideminor-conflicts-typeofchange-global": "Филтер за „мање” измене је у сукобу са једним или више филтера типа промена, зато што одређени типови промена не могу да се означе као „мање”. Сукобљени филтери су означени у подручју Активни филтери, изнад.",
"rcfilters-hideminor-conflicts-typeofchange": "Одређени типови промена не могу да се означе као „мање”, тако да је овај филтер у сукобу са следећим филтерима типа промена: $1",
"rcfilters-typeofchange-conflicts-hideminor": "Овај филтер типа измене је у сукобу са филтером за „мање” измене. Одређени типови измена не могу да се означе као „мање”.",
- "rcfilters-filtergroup-lastRevision": "Ð\9fоÑ\81ледÑ\9aе ревизије",
- "rcfilters-filter-lastrevision-label": "Ð\9fоÑ\81ледÑ\9aа измена",
+ "rcfilters-filtergroup-lastRevision": "Ð\9dаÑ\98новиÑ\98е ревизије",
+ "rcfilters-filter-lastrevision-label": "Ð\9dаÑ\98новиÑ\98а Ñ\80евизиÑ\98а",
"rcfilters-filter-lastrevision-description": "Само најновија промена на страници.",
- "rcfilters-filter-previousrevision-label": "Ð\9dиÑ\98е поÑ\81ледÑ\9aа ревизија",
+ "rcfilters-filter-previousrevision-label": "Ð\9dиÑ\98е наÑ\98новиÑ\98а ревизија",
"rcfilters-filter-previousrevision-description": "Све промене које нису „последње ревизије”.",
"rcfilters-filter-excluded": "Изузето",
"rcfilters-tag-prefix-namespace-inverted": "<strong>:није</strong> $1",
"rcfilters-liveupdates-button": "Ажурирај уживо",
"rcfilters-liveupdates-button-title-on": "Искључите ажурирања уживо",
"rcfilters-liveupdates-button-title-off": "Прикажите нове промене уживо",
- "rcfilters-watchlist-markseen-button": "Ð\9eзнаÑ\87и Ñ\81ве измене као погледане",
+ "rcfilters-watchlist-markseen-button": "Ð\9eзнаÑ\87и Ñ\81ве пÑ\80омене као погледане",
"rcfilters-watchlist-edit-watchlist-button": "Промени списак надгледаних страница",
"rcfilters-watchlist-showupdated": "Промене на страницама које нисте посетили од када је измена извршена су <strong>подебљане</strong>, с испуњеним ознакама.",
"rcfilters-preference-label": "Сакриј побољшану верзију скорашњих измена",
"fileexists": "Датотека с овим именом већ постоји. Погледајте <strong>[[:$1]]</strong> ако нисте сигурни да ли желите да је промените.\n[[$1|thumb]]",
"filepageexists": "Страница с описом ове датотеке је већ направљена овде <strong>[[:$1]]</strong>, иако датотека не постоји.\nОпис који сте навели се неће појавити на страници с описом.\nДа би се ваш опис овде нашао, потребно је да га ручно измените.\n[[$1|thumb]]",
"fileexists-extension": "Датотека са сличним називом већ постоји: [[$2|thumb]]\n* Назив датотеке коју шаљете: <strong>[[:$1]]</strong>\n* Назив постојеће датотеке: <strong>[[:$2]]</strong>\nДа ли желите да користите препознатљивије име?",
- "fileexists-thumbnail-yes": "Ð\98згледа да Ñ\98е даÑ\82оÑ\82ека Ñ\83маÑ\9aено издаÑ\9aе Ñ\81лике ''(thumbnail)''.\n[[$1|thumb]]\nÐ\9fÑ\80овеÑ\80иÑ\82е даÑ\82оÑ\82екÑ\83 <strong>[[:$1]]</strong>.\nÐ\90ко Ñ\98е пÑ\80овеÑ\80ена даÑ\82оÑ\82ека иÑ\81Ñ\82а Ñ\81лика оÑ\80игиналне велиÑ\87ине, ниÑ\98е поÑ\82Ñ\80ебно Ñ\81лаÑ\82и додаÑ\82нÑ\83 Ñ\81лику.",
- "file-thumbnail-no": "Ð\94аÑ\82оÑ\82ека поÑ\87иÑ\9aе Ñ\81а <strong>$1</strong>.\nÐ\98згледа да Ñ\81е Ñ\80ади о Ñ\83маÑ\9aеноÑ\98 Ñ\81лиÑ\86и ''(thumbnail)''.\nУколико имаÑ\82е овÑ\83 Ñ\81ликÑ\83 Ñ\83 пÑ\83ноÑ\98 велиÑ\87ини, поÑ\88аÑ\99иÑ\82е Ñ\98е, а ако немаÑ\82е, пÑ\80омениÑ\82е назив датотеке.",
+ "fileexists-thumbnail-yes": "Ð\98згледа да Ñ\98е даÑ\82оÑ\82ека Ñ\81лика Ñ\83маÑ\9aене велиÑ\87ине <em>(Ñ\81лиÑ\87иÑ\86а)</em>.\n[[$1|thumb]]\nÐ\9fÑ\80овеÑ\80иÑ\82е даÑ\82оÑ\82екÑ\83 <strong>[[:$1]]</strong>.\nÐ\90ко Ñ\98е пÑ\80овеÑ\80ена даÑ\82оÑ\82ека иÑ\81Ñ\82а Ñ\81лика пÑ\80вобиÑ\82не велиÑ\87ине, ниÑ\98е поÑ\82Ñ\80ебно оÑ\82пÑ\80емаÑ\82и додаÑ\82ну.",
+ "file-thumbnail-no": "Ð\98ме даÑ\82оÑ\82еке поÑ\87иÑ\9aе Ñ\81а <strong>$1</strong>.\nÐ\98згледа да Ñ\81е Ñ\80ади о Ñ\81лиÑ\86и Ñ\83маÑ\9aене велиÑ\87ине <em>(Ñ\81лиÑ\87иÑ\86а)</em>.\nÐ\90ко имаÑ\82е овÑ\83 Ñ\81ликÑ\83 Ñ\83 пÑ\83ноÑ\98 Ñ\80езолÑ\83Ñ\86иÑ\98и, оÑ\82пÑ\80емиÑ\82е Ñ\98е, Ñ\83 пÑ\80оÑ\82ивном, пÑ\80омениÑ\82е име датотеке.",
"fileexists-forbidden": "Датотека с овим називом већ постоји и не може се заменити.\nАко и даље желите да пошаљете датотеку, вратите се и изаберите други назив.\n[[File:$1|thumb|center|$1]]",
"fileexists-shared-forbidden": "Датотека са овим именом већ постоји у заједничкој остави.\nАко још увек желите да отпремите датотеку, вратите се и користите ново име.\n[[File:$1|thumb|center|$1]]",
"fileexists-no-change": "Датотека је дупликат актуелне верзије <strong>[[:$1]]</strong>.",
"php-uploaddisabledtext": "Отпремање датотека је онемогућено у PHP-у.\nПроверите подешавања file_uploads.",
"uploadscripted": "Датотека садржи HTML или скриптни код који може бити погрешно протумачен од стране прегледача.",
"upload-scripted-pi-callback": "Датотека која садржи инструкције за обраду XML стилског облика се не може отпремити.",
- "upload-scripted-dtd": "Ð\9dе могÑ\83 да оÑ\82пÑ\80емим SVG даÑ\82оÑ\82еке које садрже нестандардну DTD декларацију.",
+ "upload-scripted-dtd": "Ð\9dиÑ\98е могÑ\83Ñ\9bе оÑ\82пÑ\80емаÑ\9aе SVG даÑ\82оÑ\82ека које садрже нестандардну DTD декларацију.",
"uploaded-script-svg": "Пронађен скриптни елеменат „$1“ у постављеној SVG датотеци.",
"uploaded-hostile-svg": "Пронађен небезбедан CSS у стилском елементу постављене SVG датотеке.",
"uploaded-event-handler-on-svg": "Није дозвољено постављање атрибута који контролишу догађаје <code>$1=\"$2\"</code> у SVG датотекама.",
"uploadstash-badtoken": "Извршавање ове радње није успело, разлог томе може бити истек времена за уређивање. Покушајте поново.",
"uploadstash-errclear": "Чишћење датотека није успело.",
"uploadstash-refresh": "Освежи списак датотека",
- "uploadstash-thumbnail": "погледај минијатуру",
+ "uploadstash-thumbnail": "погледај сличицу",
"uploadstash-exception": "Не могу сачувати датотеку у складиште ($1): „$2“.",
"uploadstash-bad-path": "Путања не постоји.",
"uploadstash-bad-path-invalid": "Путања није валидна.",
"uploadstash-bad-path-unrecognized-thumb-name": "Непрепознато име минијатуре.",
"uploadstash-bad-path-bad-format": "Кључ „$1“ није у одговарајућем облику.",
"uploadstash-file-not-found": "Кључ „$1” није пронађен у складишту.",
- "uploadstash-file-not-found-no-thumb": "Ð\9dе могÑ\83 добиÑ\82и миниÑ\98аÑ\82Ñ\83Ñ\80у.",
+ "uploadstash-file-not-found-no-thumb": "Ð\9dе могÑ\83 да пÑ\80ибавим Ñ\81лиÑ\87иÑ\86у.",
"uploadstash-file-not-found-no-local-path": "Нема локалне путање за умањену ставку.",
- "uploadstash-file-not-found-no-object": "Ð\9dе могÑ\83 напÑ\80авиÑ\82и локални даÑ\82оÑ\82еÑ\87ни обÑ\98екаÑ\82 за миниÑ\98аÑ\82Ñ\83Ñ\80у.",
+ "uploadstash-file-not-found-no-object": "Ð\9dе могÑ\83 да напÑ\80авим локални даÑ\82оÑ\82еÑ\87ни обÑ\98екаÑ\82 за Ñ\81лиÑ\87иÑ\86у.",
"uploadstash-file-not-found-no-remote-thumb": "Добављање минијатуре није успело: $1\nАдреса = $2",
"uploadstash-file-not-found-missing-content-type": "Недостаје заглавље за тип садржаја.",
"uploadstash-file-not-found-not-exists": "Не могу наћи путању или ово није обична датотека.",
"listfiles-userdoesnotexist": "Кориснички налог „$1“ није отворен.",
"imgfile": "датотека",
"listfiles": "Списак датотека",
- "listfiles_thumb": "Ð\9cиниÑ\98аÑ\82Ñ\83Ñ\80а",
+ "listfiles_thumb": "СлиÑ\87иÑ\86а",
"listfiles_date": "Датум",
"listfiles_name": "Назив",
"listfiles_user": "Корисник",
"filehist-revert": "врати",
"filehist-current": "актуелна",
"filehist-datetime": "Датум/време",
- "filehist-thumb": "Ð\9cиниÑ\98аÑ\82Ñ\83Ñ\80а",
+ "filehist-thumb": "СлиÑ\87иÑ\86а",
"filehist-thumbtext": "Минијатура за верзију на дан $1",
- "filehist-nothumb": "Ð\9dема Ñ\83маÑ\9aеног пÑ\80иказа",
+ "filehist-nothumb": "Ð\91ез Ñ\81лиÑ\87иÑ\86е",
"filehist-user": "Корисник",
"filehist-dimensions": "Димензије",
"filehist-filesize": "Величина датотеке",
"ipb-confirm": "Потврди блокирање",
"badipaddress": "Неважећа IP адреса",
"blockipsuccesssub": "Блокирање је успело",
- "blockipsuccesstext": "[[Special:Contributions/$1|$1]] је {{GENDER:$1|блокиран|блокирана|блокиран}}.<br />\nБлокирања можете да погледате [[Special:BlockList|овде]].",
+ "blockipsuccesstext": "[[Special:Contributions/$1|$1]] је {{GENDER:$1|блокиран|блокирана}}.<br />\nПогледајте [[Special:BlockList|списак]] за преглед блокада.",
"ipb-blockingself": "Овом радњом ћете блокирати себе! Јесте ли сигурни да то желите?",
"ipb-confirmhideuser": "Управо ћете блокирати корисника с укљученом могућношћу „сакриј корисника“. Овим ће корисничко име бити сакривено у свим списковима и извештајима. Желите ли то да урадите?",
"ipb-confirmaction": "Ако сте сигурни да желите наставити означите поље „{{int:ipb-confirm}}“ на дну странице.",
"movepage-moved-redirect": "Преусмерење је направљено.",
"movepage-moved-noredirect": "Стварање преусмерења је онемогућено.",
"articleexists": "Страница са тим именом већ постоји или име које сте одабрали није важеће.\nОдаберите друго.",
- "cantmove-titleprotected": "Не можете да преместите страницу на то место јер је жељени наслов заштићен од стварања",
+ "cantmove-titleprotected": "Не можете да преместите страницу на ову локацију јер је прављење новог наслова заштићено.",
"movetalk": "Премести и страницу за разговор",
"move-subpages": "Премести и подстранице (до $1)",
"move-talk-subpages": "Премести подстранице странице за разговор (до $1)",
"allmessages-filter-translate": "Преведи",
"thumbnail-more": "Повећајте",
"filemissing": "Недостаје датотека",
- "thumbnail_error": "Грешка при стварању минијатуре: $1",
+ "thumbnail_error": "Грешка при прављењу сличице: $1",
"thumbnail_error_remote": "Порука о грешци из $1:\n$2",
"djvu_page_error": "DjVu страница је ван опсега",
"djvu_no_xml": "Не могу да преузмем XML за DjVu датотеку.",
- "thumbnail-temp-create": "Ð\9dе могÑ\83 да напÑ\80авим пÑ\80ивÑ\80еменÑ\83 даÑ\82оÑ\82екÑ\83 миниÑ\98аÑ\82Ñ\83Ñ\80е",
+ "thumbnail-temp-create": "Ð\9dе могÑ\83 да напÑ\80авим пÑ\80ивÑ\80еменÑ\83 даÑ\82оÑ\82екÑ\83 за Ñ\81лиÑ\87иÑ\86Ñ\83",
"thumbnail-dest-create": "Не могу да сачувам минијатуру у одредишту",
"thumbnail_invalid_params": "Неважећи параметри сличице",
"thumbnail_toobigimagearea": "Датотека са величинама већим од $1",
"thumbnail_gd-library": "Недовршена подешавања графичке библиотеке: недостаје функција $1",
"thumbnail_image-size-zero": "Изгледа да је величина датотеке нула.",
"thumbnail_image-missing": "Датотека недостаје: $1",
- "thumbnail_image-failure-limit": "Било је превише недавних неуспешних покушаја ($1 или више) рендеровања ове минијатуре. Покушајте поново касније.",
+ "thumbnail_image-failure-limit": "Било је превише недавних неуспелих покушаја ($1 или више) рендеровања ове сличице. Покушајте поново касније.",
"import": "Увоз страница",
"importinterwiki": "Увоз са другог викија",
"import-interwiki-text": "Изаберите вики и наслов странице за увоз.\nДатуми ревизија и имена уредника ће бити сачувани.\nСве радње при увозу с других викија су евидентиране у [[Special:Log/import|евиденцији увоза]].",
"tooltip-n-currentevents": "Пронађите додатне информације о актуелностима",
"tooltip-n-recentchanges": "Списак недавних промена на викију",
"tooltip-n-randompage": "Учитајте случајну страницу",
- "tooltip-n-help": "Ð\9cеÑ\81Ñ\82о где можеÑ\82е да наÑ\83Ñ\87иÑ\82е неÑ\88Ñ\82о",
+ "tooltip-n-help": "Ð\9cеÑ\81Ñ\82о где можеÑ\82е неÑ\88Ñ\82о да наÑ\83Ñ\87иÑ\82е",
"tooltip-t-whatlinkshere": "Списак свих вики страница које воде овде",
"tooltip-t-recentchangeslinked": "Недавне промене на страницама које су повезане с овом страницом",
"tooltip-feed-rss": "RSS фид за ову страницу",
"common.js": "/* Јаваскрипт постављен овде ће се користити за све кориснике при отварању сваке странице. */",
"group-autoconfirmed.js": "/* Јаваскрипт постављен овде ће се учитати за самопотврђене кориснике */",
"group-bot.js": "/* Јаваскрипт постављен овде ће се учитати само за ботове */",
- "group-sysop.js": "/* Јаваскрипт постављен овде ће се учитати само за системске операторе */",
+ "group-sysop.js": "/* JavaScript постављен овде ће се учитати само за системске операторе */",
"group-bureaucrat.js": "/* Јаваскрипт постављен овде ће се учитати само за бирократе */",
"anonymous": "Анонимни {{PLURAL:$1|корисник|корисници}} пројекта {{SITENAME}}",
"siteuser": "{{SITENAME}} корисник $1",
"nextdiff": "Новија измена →",
"mediawarning": "<strong>Упозорење:</strong> овај тип датотеке може да садржи штетан код.\nЊеговим извршавањем можете да угрозите ваш систем.",
"imagemaxsize": "Ограничење величине слике:<br /><em>(на страницама за опис датотека)</em>",
- "thumbsize": "Величина минијатуре:",
+ "thumbsize": "Величина сличице:",
"widthheight": "$1 × $2",
"widthheightpage": "$1 × $2, $3 {{PLURAL:$3|страница|странице|страница}}",
"file-info": "величина датотеке: $1, MIME тип: $2",
"file-info-png-looped": "петља",
"file-info-png-repeat": "поновљено $1 {{PLURAL:$1|пут|пута|пута}}",
"file-info-png-frames": "$1 {{PLURAL:$1|кадар|кадра|кадрова}}",
- "file-no-thumb-animation": "'''Напомена: због техничких ограничења, минијатуре ове датотеке се неће анимирати.'''",
+ "file-no-thumb-animation": "<strong>Напомена: Због техничких ограничења, сличице ове датотеке неће да се анимирају.</strong>",
"file-no-thumb-animation-gif": "'''Напомена: због техничких ограничења, минијатуре GIF слика високе резолуције као што је ова неће се анимирати.'''",
"newimages": "Галерија нових датотека",
"imagelisttext": "Испод је списак од '''$1''' {{PLURAL:$1|датотеке|датотеке|датотека}} поређаних $2.",
"newimages-hidepatrolled": "Сакриј патролирана отпремања",
"newimages-mediatype": "Тип датотеке:",
"noimages": "Нема ништа.",
- "gallery-slideshow-toggle": "минијатуре",
+ "gallery-slideshow-toggle": "сличице",
"ilsubmit": "Претражи",
"bydate": "по датуму",
"sp-newimages-showfrom": "прикажи нове датотеке почевши од $1, $2",
"exif-ycbcrsubsampling": "Однос величине Y према C",
"exif-ycbcrpositioning": "Положај Y и C",
"exif-xresolution": "Водоравна резолуција",
- "exif-yresolution": "УÑ\81пÑ\80авна резолуција",
- "exif-stripoffsets": "Ð\9cеÑ\81Ñ\82о подаÑ\82ака",
+ "exif-yresolution": "Ð\92еÑ\80Ñ\82икална резолуција",
+ "exif-stripoffsets": "Ð\9bокаÑ\86иÑ\98а подаÑ\82ака Ñ\81лике",
"exif-rowsperstrip": "Број редова по линији",
"exif-stripbytecounts": "Бајтова по сажетом блоку",
"exif-jpeginterchangeformat": "Почетак JPEG прегледа",
"exif-urgency": "Хитност",
"exif-fixtureidentifier": "Назив рубрике",
"exif-locationdest": "Приказана локација",
- "exif-locationdestcode": "Код приказаног места",
+ "exif-locationdestcode": "Кôд приказане локације",
"exif-objectcycle": "Доба дана за који је медиј намењен",
"exif-contact": "Подаци за контакт",
"exif-writer": "Писац",
"confirm-unwatch-top": "Уклонити ову страницу са списка надгледања?",
"confirm-rollback-button": "У реду",
"confirm-rollback-top": "Врати измене на овој страници?",
+ "confirm-mcrundo-title": "Поништавање промене",
+ "mcrundofailed": "Поништавање није успело",
+ "mcrundo-missingparam": "Недостаје потребан параметар на захтеву.",
+ "mcrundo-changed": "Страница је промењена док сте гледали разлику. Прегледајте нову промену.",
"semicolon-separator": "; ",
"comma-separator": ", ",
"colon-separator": ": ",
"version-libraries-license": "Лиценца",
"version-libraries-description": "Опис",
"version-libraries-authors": "Аутори",
- "redirect": "Ð\9fÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aе на даÑ\82оÑ\82екÑ\83, коÑ\80иÑ\81ника, Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, Ñ\80евизиÑ\98Ñ\83 или дневник (ID)",
+ "redirect": "Ð\9fÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aе на даÑ\82оÑ\82екÑ\83, коÑ\80иÑ\81ника, Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, Ñ\80евизиÑ\98Ñ\83 или евиденÑ\86иÑ\98Ñ\83 (ID)",
"redirect-summary": "Ова посебна страница преусмерава до датотеке (с датим именом датотеке), странице (с датим ID-ом ревизије или ID-ом странице), корисничке странице (с датим нумеричким корисничким ID-ом), или уноса у дневнику (с датим дневничким ID-ом). Употреба: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]], or [[{{#Special:Redirect}}/logid/186]].",
"redirect-submit": "Иди",
"redirect-lookup": "Тип вредности:",
"log-description-tag": "Ова страница приказује када су корисници додали/уклонили [[Special:Tags|ознаке]] с појединачних ревизија или уноса у евиденцијама. Евиденција не приказује радње означавања када су се догодиле приликом уређивања, брисања или сличне радње.",
"rightsnone": "(нема)",
"rightslogentry-temporary-group": "$1 (привремено, до $2)",
- "feedback-adding": "Додајем повратну информацију на страницу…",
+ "feedback-adding": "Додајем повратне информације на страницу…",
"feedback-back": "Назад",
- "feedback-bugcheck": "Одлично! Проверите да ли је грешка [$1 позната од пре].",
+ "feedback-bugcheck": "Одлично! Проверите да се не ради о некој [$1 познатој грешци].",
"feedback-bugnew": "Проверено. Пријави нову грешку",
"feedback-bugornote": "Ако сте спремни да детаљно опишете технички проблем, онда [$1 пријавите грешку].\nУ супротном, послужите се једноставним обрасцем испод. Ваш коментар ће стајати на страници „[$3 $2]“, заједно с корисничким именом и прегледачем који користите.",
"feedback-cancel": "Откажи",
- "feedback-close": "УÑ\80аÑ\92ено",
- "feedback-external-bug-report-button": "Ð\9fÑ\80иÑ\98ави гÑ\80еÑ\88кÑ\83",
- "feedback-dialog-title": "СлаÑ\9aе повÑ\80аÑ\82не инÑ\84оÑ\80маÑ\86иÑ\98е",
- "feedback-error1": "Грешка: непрепознат резултат од АПИ-ја",
+ "feedback-close": "Ð\93оÑ\82ово",
+ "feedback-external-bug-report-button": "Ð\90Ñ\80Ñ\85ивиÑ\80аÑ\98 Ñ\82еÑ\85ниÑ\87ки задаÑ\82ак",
+ "feedback-dialog-title": "СлаÑ\9aе повÑ\80аÑ\82ниÑ\85 инÑ\84оÑ\80маÑ\86иÑ\98а",
+ "feedback-error1": "Грешка: непрепознат резултат од API-ја",
"feedback-error2": "Грешка: уређивање није успело",
- "feedback-error3": "Грешка: нема одговора од АПИ-ја",
+ "feedback-error3": "Грешка: нема одговора од API-ја",
+ "feedback-error4": "Грешка: не могу да поставим повратне информације на дати наслов",
"feedback-message": "Порука:",
- "feedback-subject": "Ð\9dаÑ\81лов:",
+ "feedback-subject": "Тема:",
"feedback-submit": "Пошаљи",
"feedback-termsofuse": "Прихватам да пошаљем повратне информације у складу са условима коришћења.",
"feedback-thanks": "Хвала! Ваша повратна информација је постављена на страницу „[$2 $1]“.",
"searchsuggest-search": "Претрага",
"searchsuggest-containing": "садржи…",
"api-error-badtoken": "Унутрашња грешка: лош токен.",
- "api-error-emptypage": "СÑ\82ваÑ\80ање нових празних страница није дозвољено.",
+ "api-error-emptypage": "Ð\9fÑ\80авÑ\99ење нових празних страница није дозвољено.",
"api-error-publishfailed": "Унутрашња грешка: сервер није успео да објави привремену датотеку.",
- "api-error-stashfailed": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aа гÑ\80еÑ\88ка: Ñ\81еÑ\80веÑ\80 не може да Ñ\81аÑ\87Ñ\83ва привремену датотеку.",
+ "api-error-stashfailed": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aа гÑ\80еÑ\88ка: Ñ\81еÑ\80веÑ\80 ниÑ\98е Ñ\83Ñ\81пео да Ñ\81меÑ\81Ñ\82и привремену датотеку.",
"api-error-unknown-warning": "Непознато упозорење: „$1”.",
- "api-error-unknownerror": "Ð\9dепознаÑ\82а гÑ\80еÑ\88ка: â\80\9e$1â\80\9c.",
+ "api-error-unknownerror": "Ð\9dепознаÑ\82а гÑ\80еÑ\88ка: â\80\9e$1â\80\9d.",
"duration-seconds": "$1 {{PLURAL:$1|секунд|секунда|секунди}}",
- "duration-minutes": "$1 {{PLURAL:$1|минут|минута|минута}}",
+ "duration-minutes": "$1 {{PLURAL:$1|минут|минута}}",
"duration-hours": "$1 {{PLURAL:$1|сат|сата|сати}}",
- "duration-days": "$1 {{PLURAL:$1|дан|дана|дана}}",
+ "duration-days": "$1 {{PLURAL:$1|дан|дана}}",
"duration-weeks": "$1 {{PLURAL:$1|недеља|недеље|недеља}}",
"duration-years": "$1 {{PLURAL:$1|година|године|година}}",
"duration-decades": "$1 {{PLURAL:$1|деценија|деценије|деценија}}",
"duration-centuries": "$1 {{PLURAL:$1|век|века|векова}}",
- "duration-millennia": "$1 {{PLURAL:$1|миленијум|миленијума|миленијума}}",
- "rotate-comment": "Слика је ротирана за $1° у смеру казаљке на сату",
+ "duration-millennia": "$1 {{PLURAL:$1|миленијум|миленијума}}",
+ "rotate-comment": "Слика је ротирана за $1 {{PLURAL:$1|степен|степена|степени}} у смеру казаљке на сату",
"limitreport-title": "Подаци профилисања анализатора:",
- "limitreport-cputime": "Време коришћења CPU",
+ "limitreport-cputime": "Време коришћења CPU-а",
"limitreport-cputime-value": "$1 {{PLURAL:$1|секунд|секунда|секунди}}",
"limitreport-walltime": "Коришћење у реалном времену",
"limitreport-walltime-value": "$1 {{PLURAL:$1|секунд|секунда|секунди}}",
"limitreport-unstrip-size-value": "$1/$2 {{PLURAL:$2|бајт|бајта|бајтова}}",
"expandtemplates": "Проширавање шаблона",
"expand_templates_intro": "Ова посебна страница узима викитекст и мења све шаблоне у њему рекурзивно.\nТакође мења функције парсера као што је <code><nowiki>{{</nowiki>#language:…}}</code> и променљиве као што је <code><nowiki>{{</nowiki>CURRENTDAY}}</code>. \nЗаправо практично све што се налази између витичастих заграда.",
- "expand_templates_title": "Назив контекста; за {{СТРАНИЦА}} итд.:",
+ "expand_templates_title": "Наслов контекста, за {{FULLPAGENAME}} итд.:",
"expand_templates_input": "Унос викитекста:",
"expand_templates_output": "Резултат",
"expand_templates_xml_output": "XML излаз",
"expand_templates_ok": "У реду",
"expand_templates_remove_comments": "Уклони коментаре",
"expand_templates_remove_nowiki": "Поништава ефекат <nowiki> тагова у приказу чланака",
- "expand_templates_generate_xml": "Прикажи XML стабло",
+ "expand_templates_generate_xml": "Прикажи XML стабло за рашчлањивање",
"expand_templates_generate_rawhtml": "Прикажи сиров HTML",
"expand_templates_preview": "Претпреглед",
"pagelanguage": "Промена језика странице",
"pagelang-select-lang": "Изабери језик",
"pagelang-reason": "Разлог",
"pagelang-submit": "Пошаљи",
- "pagelang-nonexistent-page": "Страница $1 не постоји.",
- "pagelang-unchanged-language": "Страница $1 је већ постављена на језик $2.",
- "pagelang-db-failed": "Ð\91аза подаÑ\82ака ниÑ\98е Ñ\83Ñ\81пела пÑ\80омениÑ\82и језик странице.",
+ "pagelang-nonexistent-page": "Страница „$1” не постоји.",
+ "pagelang-unchanged-language": "Страница „$1” је већ постављена на језик $2.",
+ "pagelang-db-failed": "Ð\91аза подаÑ\82ака ниÑ\98е Ñ\83Ñ\81пела да пÑ\80Ð¾Ð¼ÐµÐ½и језик странице.",
"right-pagelang": "мењање језика странице",
"action-pagelang": "промените језик странице",
"log-name-pagelang": "Евиденција промене језика",
"log-description-pagelang": "Ово је евиденција промена у језицима страница.",
- "logentry-pagelang-pagelang": "$1 је {{GENDER:$2|променио|променила}} језик странице $3 из $4 у $5.",
+ "logentry-pagelang-pagelang": "$1 је {{GENDER:$2|променио|променила}} језик странице „$3” из $4 у $5.",
"default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (омогућена)",
"default-skin-not-found-row-disabled": "* <code>$1</code> / $2 (<strong>онемогућена</strong>)",
"mediastatistics": "Статистика медија",
"mediastatistics-table-mimetype": "MIME тип",
"mediastatistics-table-extensions": "Могући додаци",
"mediastatistics-table-count": "Број датотека",
- "mediastatistics-table-totalbytes": "УкÑ\83пна величина",
+ "mediastatistics-table-totalbytes": "Ð\9aомбинована величина",
"mediastatistics-header-unknown": "Непознато",
"mediastatistics-header-bitmap": "Битмап слике",
"mediastatistics-header-drawing": "Цртежи (векторске слике)",
- "mediastatistics-header-audio": "Ð\90Ñ\83дио",
- "mediastatistics-header-video": "Ð\92идео",
+ "mediastatistics-header-audio": "Ð\97вÑ\83к",
+ "mediastatistics-header-video": "Ð\92идеи",
"mediastatistics-header-multimedia": "Обогаћени медији",
"mediastatistics-header-office": "Канцеларија",
"mediastatistics-header-text": "Текстуалне",
"mediastatistics-header-executable": "Извршне",
- "mediastatistics-header-archive": "Ð\9aомпÑ\80еÑ\81оване",
+ "mediastatistics-header-archive": "Ð\9aомпÑ\80еÑ\81овани Ñ\84оÑ\80маÑ\82и",
"mediastatistics-header-total": "Све датотеке",
"json-warn-trailing-comma": "$1 {{PLURAL:$1|пратећа тачка је уклоњена|пратеће тачке су уклоњене|пратећих тачки је уклоњено}} из JSON-a",
"json-error-unknown": "Догодио се проблем с JSON-ом. Грешка: $1",
"special-characters-group-cyrillic": "Ћирилица",
"special-characters-group-arabic": "Арапски",
"special-characters-group-arabicextended": "Проширени арапски",
- "special-characters-group-persian": "персијски",
+ "special-characters-group-persian": "Ð\9fерсијски",
"special-characters-group-hebrew": "Хебрејски",
"special-characters-group-bangla": "Бенгалски",
"special-characters-group-tamil": "Тамилски",
"special-characters-group-khmer": "Кмерски",
"special-characters-group-canadianaboriginal": "Канадски абориџински",
"special-characters-title-endash": "цртица",
- "special-characters-title-emdash": "дÑ\83га Ñ\86Ñ\80Ñ\82иÑ\86а",
- "special-characters-title-minus": "минус",
+ "special-characters-title-emdash": "дуга црта",
+ "special-characters-title-minus": "знак за минÑ\83Ñ\81",
"mw-widgets-dateinput-no-date": "Датум није изабран",
"mw-widgets-dateinput-placeholder-day": "ГГГГ-ММ-ДД",
"mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ",
- "mw-widgets-mediasearch-input-placeholder": "Ð\9fÑ\80еÑ\82Ñ\80ажиÑ\82е даÑ\82оÑ\82еке",
- "mw-widgets-mediasearch-noresults": "Ð\9dема Ñ\80езÑ\83лÑ\82аÑ\82а.",
+ "mw-widgets-mediasearch-input-placeholder": "Ð\9fÑ\80еÑ\82Ñ\80ажиÑ\82е медиÑ\98е",
+ "mw-widgets-mediasearch-noresults": "РезÑ\83лÑ\82аÑ\82и ниÑ\81Ñ\83 пÑ\80онаÑ\92ени.",
"mw-widgets-titleinput-description-new-page": "страница још увек не постоји",
"mw-widgets-titleinput-description-redirect": "преусмерава на $1",
"mw-widgets-categoryselector-add-category-placeholder": "Додајте категорију…",
- "mw-widgets-usersmultiselect-placeholder": "Додај још...",
+ "mw-widgets-usersmultiselect-placeholder": "Додајте још…",
"date-range-from": "Од датума:",
"date-range-to": "До датума:",
+ "sessionmanager-tie": "Не можете да комбинујете више типова потврде идентитета: $1.",
"sessionprovider-generic": "$1 сесије",
"sessionprovider-mediawiki-session-cookiesessionprovider": "сесије са колачићима",
- "sessionprovider-nocookies": "Колачићи су можда онемогућени. Уверите се да су колачићи омогућени и почните поново.",
+ "sessionprovider-nocookies": "Колачићи су можда онемогућени. Уверите се да имате колачиће омогућене и почните поново.",
"randomrootpage": "Случајна коренска страница",
"log-action-filter-block": "Тип блокирања:",
"log-action-filter-contentmodel": "Тип промене модела садржаја:",
"log-action-filter-import": "Тип увоза:",
"log-action-filter-managetags": "Тип радње управљања ознакама:",
"log-action-filter-move": "Тип премештања:",
- "log-action-filter-newusers": "Тип новог налога:",
+ "log-action-filter-newusers": "Тип оÑ\82ваÑ\80аÑ\9aа налога:",
"log-action-filter-patrol": "Тип патролирања:",
"log-action-filter-protect": "Тип заштите:",
"log-action-filter-rights": "Тип промене корисничких права:",
"log-action-filter-block-reblock": "измена блокирања",
"log-action-filter-block-unblock": "деблокирање",
"log-action-filter-contentmodel-change": "Промена модела садржаја",
- "log-action-filter-contentmodel-new": "Ð\9dова Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81 нестандардним моделом садржаја",
+ "log-action-filter-contentmodel-new": "Ð\9fÑ\80авÑ\99еÑ\9aе Ñ\81Ñ\82Ñ\80аниÑ\86е Ñ\81а нестандардним моделом садржаја",
"log-action-filter-delete-delete": "брисање странице",
"log-action-filter-delete-delete_redir": "преснимавање преусмерења",
"log-action-filter-delete-restore": "враћање странице",
"log-action-filter-delete-revision": "брисање ревизија",
"log-action-filter-import-interwiki": "Међувики увоз",
"log-action-filter-import-upload": "Увоз постављањем XML-а",
- "log-action-filter-managetags-create": "нова ознака",
+ "log-action-filter-managetags-create": "пÑ\80авÑ\99еÑ\9aе ознаке",
"log-action-filter-managetags-delete": "брисање ознаке",
"log-action-filter-managetags-activate": "активирање ознаке",
"log-action-filter-managetags-deactivate": "деактивирање ознаке",
"authmanager-authn-no-local-user-link": "Пружени су важећи акредитиви, али нису повезани ни с једним корисником на овом викију. Пријавите се на неки други начин или направите нови кориснички налог, што ће вам дати могућност да повежете претходне акредитиве на нови налог.",
"authmanager-authn-autocreate-failed": "Не могу да аутоматски направим локални налог: $1",
"authmanager-change-not-supported": "Не могу да променим пружене акредитиве јер их ништа не би користило.",
- "authmanager-create-disabled": "Онемогућено прављење налога.",
+ "authmanager-create-disabled": "Отварање налога је онемогућено.",
"authmanager-create-from-login": "Попуните поља да бисте направили налог.",
- "authmanager-create-not-in-progress": "Ð\9fÑ\80авÑ\99еÑ\9aе налога ниÑ\98е Ñ\83 Ñ\82окÑ\83 или Ñ\81Ñ\83 подаÑ\86и о Ñ\81еÑ\81иÑ\98и изгÑ\83бÑ\99ени. Ð\9fоÑ\87ниÑ\82е испочетка.",
- "authmanager-create-no-primary": "Ð\9dе могÑ\83 да иÑ\81коÑ\80иÑ\81Ñ\82им пÑ\80Ñ\83жене акÑ\80едиÑ\82иве за пÑ\80авÑ\99ење налога.",
+ "authmanager-create-not-in-progress": "Ð\9eÑ\82ваÑ\80аÑ\9aе налога ниÑ\98е Ñ\83 Ñ\82окÑ\83 или Ñ\81Ñ\83 подаÑ\86и о Ñ\81еÑ\81иÑ\98и изгÑ\83бÑ\99ени. Ð\9fоÑ\87ниÑ\82е поново испочетка.",
+ "authmanager-create-no-primary": "Ð\9dе могÑ\83 да иÑ\81коÑ\80иÑ\81Ñ\82им пÑ\80Ñ\83жене акÑ\80едиÑ\82иве за оÑ\82ваÑ\80ање налога.",
"authmanager-link-no-primary": "Не могу да искористим пружене акредитиве за спајање налога.",
"authmanager-link-not-in-progress": "Спајање налога није у току или је дошло до губитка података о сесији. Почните испочетка.",
"authmanager-authplugin-setpass-failed-title": "Неуспешна промена лозинке",
"authmanager-authplugin-setpass-failed-message": "Додатак за потврду идентитета је одбио промену лозинке.",
- "authmanager-authplugin-create-fail": "Ð\94одаÑ\82ак за поÑ\82вÑ\80дÑ\83 иденÑ\82иÑ\82еÑ\82а Ñ\98е одбио пÑ\80авÑ\99ење налога.",
+ "authmanager-authplugin-create-fail": "Ð\94одаÑ\82ак за поÑ\82вÑ\80дÑ\83 иденÑ\82иÑ\82еÑ\82а Ñ\98е одбио оÑ\82ваÑ\80ање налога.",
"authmanager-authplugin-setpass-denied": "Додатак за потврду идентитета не дозвољава мењање лозику.",
"authmanager-authplugin-setpass-bad-domain": "Неважећи домен.",
- "authmanager-autocreate-noperm": "Ð\90Ñ\83Ñ\82омаÑ\82Ñ\81ко пÑ\80авÑ\99ење налога није дозвољено.",
+ "authmanager-autocreate-noperm": "Ð\90Ñ\83Ñ\82омаÑ\82Ñ\81ко оÑ\82ваÑ\80ање налога није дозвољено.",
"authmanager-userdoesnotexist": "Кориснички налог „$1“ није отворен.",
"authmanager-username-help": "Корисничко име за потврду идентитета.",
"authmanager-password-help": "Лозинка за потврду идентитета.",
"authmanager-domain-help": "Домен за спољашњу потврду идентитета.",
"authmanager-retype-help": "Поновите лозинку да би сте потврдили.",
"authmanager-email-label": "Имејл",
- "authmanager-email-help": "Имејл адреса",
+ "authmanager-email-help": "Имејл-адреса",
"authmanager-realname-label": "Право име",
"authmanager-realname-help": "Право име корисника",
"authmanager-provider-password": "Потврда идентитета лозинком",
"cascadeprotected": "Ova stranica je zaključana jer sadrži {{PLURAL:$1|sledeću stranicu koja je zaštićena|sledeće stranice koje su zaštićene}} „prenosivom“ zaštitom:\n$2",
"namespaceprotected": "Nemate dozvolu da uređujete stranice u imenskom prostoru: <strong>$1</strong>.",
"customcssprotected": "Nemate dozvolu da menjate ovu CSS stranicu jer sadrži lična podešavanja drugog korisnika.",
+ "customjsonprotected": "Nemate dozvolu da menjate ovu JSON stranicu jer sadrži lična podešavanja drugog korisnika.",
"customjsprotected": "Nemate dozvolu da menjate ovu stranicu JavaScript jer sadrži lična podešavanja drugog korisnika.",
"mycustomcssprotected": "Nemate dozvolu za menjanje ove CSS stranice.",
"mycustomjsonprotected": "Nemate dozvolu za menjanje ove JSON stranice.",
"cascadeprotected": "Ta zajta je chrōniōnŏ ôd edycyje, skuli tego co je ôna wkludzōnŏ do {{PLURAL:$1|nastympujōncyj zajty, kerŏ ôstała ôchrōniōnŏ|nastympujōncych zajtach, kere ôstały ôchrōniōne}} ze załōnczōnōm ôpcyjōm erbowaniŏ:\n$2",
"namespaceprotected": "Ńy mosz uprowńyń, coby sprowjać zajty we raumje mjan '''$1'''.",
"customcssprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty, bo na ńij sům uosobiste sztalowańo inkszego użytkowńika.",
+ "customjsonprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty, bo na ńij sům uosobiste sztalowańo inkszego użytkowńika.",
"customjsprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty, bo na ńij sům uosobiste sztalowańo inkszego użytkowńika.",
"mycustomcssprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty CSS.",
+ "mycustomjsonprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty JSON.",
"mycustomjsprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty JavaScript.",
"myprivateinfoprotected": "Ńy mosz uprowńyń coby sprowjić swoje prywatne dane.",
"mypreferencesprotected": "Ńy mosz uprowńyń coby sprowjić swoje sztalowańo.",
"proxyblockreason": "మీ ఐపీ అడ్రసు ఒక ఓపెన్ ప్రాక్సీ కాబట్టి దాన్ని నిరోధించాం. మీ ఇంటర్నెట్ సేవాదారుని గానీ, సాంకేతిక సహాయకుని గానీ సంప్రదించి తీవ్రమైన ఈ భద్రతా వైఫల్యాన్ని గురించి తెలపండి.",
"sorbsreason": "{{SITENAME}} వాడే DNSBLలో మీ ఐపీ అడ్రసు ఒక ఓపెన్ ప్రాక్సీగా నమోదై ఉంది.",
"sorbs_create_account_reason": "మీ ఐపీ అడ్రసు DNSBL లో ఓపెను ప్రాక్సీగా నమోదయి ఉంది. మీరు ఎకౌంటును సృష్టించజాలరు.",
+ "softblockrangesreason": "మీ ఐపీ అడ్రసు ($1) నుండి అజ్ఞాతంగా చేసే మార్పులకు అనుమతి లేదు. దయచేసి లాగినవండి.",
"cant-see-hidden-user": "మీరు నిరోధించదలచిన వాడుకరి ఇప్పటికే నిరోధించబడి, దాచబడి ఉన్నారు. మీకు హక్కు లేదు కాబట్టి, ఆ వాడుకరి నిరోధాన్ని చూడటంగానీ, దాన్ని మార్చడంగానీ చెయ్యలేరు.",
"ipbblocked": "మీరు ఇతర వాడుకరులని నిరోధించలేరు లేదా అనిరోధించలేరు, ఎందుకంటే మిమ్మల్ని మీరే నిరోధించుకున్నారు",
"ipbnounblockself": "మిమ్మల్ని మీరే అనిరోధించుకునే అనుమతి మీకు లేదు",
"imported-log-entries": "$1 {{PLURAL:$1|చిట్టా పద్దు దిగుమతయ్యింది|చిట్టా పద్దులు దిగుమతయ్యాయి}}.",
"importfailed": "దిగుమతి కాలేదు: $1",
"importunknownsource": "దిగుమతి చేసుకుంటున్న దాని మాతృక రకం తెలియదు",
- "importcantopen": "దిగుమతి చేయబోతున్న ఫైలును తెరవలేకపోతున్నాను",
+ "importnoprefix": "అంతర్వికీ ఆదిపదం (ప్రిఫిక్స్) ఇవ్వలేదు",
+ "importcantopen": "దిగుమతి చేయదలచిన ఫైలును తెరవలేకపోయాం",
"importbadinterwiki": "చెడు అంతర్వికీ లింకు",
"importsuccess": "దిగుమతి పూర్తయ్యింది!",
"importnosources": "ఏ వికీనుండి దిగుమతి చేసుకోవాలో సూచించలేదు. సూటి చరిత్ర ఎక్కింపులను అచేతనం చేసాం.",
"import-nonewrevisions": "కూర్పులేవీ దిగుమతి కాలేదు (అవన్నీ ఈసరికే ఉండి ఉండాలి, లేదా లోపాల కారణంగా వదిలెయ్యబడ్డాయి).",
"xml-error-string": "$1 $2వ లైనులో, వరుస $3 ($4వ బైటు): $5",
"import-upload": "XML డేటాను అప్లోడు చెయ్యి",
- "import-token-mismatch": "సెషను డేటా పోయింది.\n\nమీరు లాగౌటై పోయి ఉండవచ్చు. <strong>లాగినై ఉన్నారో లేదో చూసుకుని, మళ్ళీ ప్రయత్నించండి</strong>.\nఅది కూడా పనిచెయ్యకపోతే, ఓసారి [[Special:UserLogout|లాగౌటై]] మళ్ళీ లాగినవండి. మీ బ్రౌజరు ఈ సైటు యొక్క కూకీలను అనుమతిస్తుందని నిర్ధారించుకోండి.",
+ "import-token-mismatch": "సెషను డేటా పోయింది.\n\nమీరు లాగౌటై పోయి ఉండవచ్చు. '''లాగినై ఉన్నారో లేదో చూసుకుని, మళ్ళీ ప్రయత్నించండి'''.\nఅయినా పనిచెయ్యకపోతే, ఓసారి [[Special:UserLogout|లాగౌటై]] మళ్ళీ లాగినవండి. మీ బ్రౌజరు ఈ సైటునుండి కూకీలను అనుమతిస్తోందో లేదో చూసుకోండి.",
"import-invalid-interwiki": "మీరు చెప్పిన వికీనుండి దిగుమతి చేయలేము.",
"import-error-edit": "\"$1\" పేజీలో మార్పుచేర్పులు చేసే అనుమతి మీకు లేదు కాబట్టి, దాన్ని దిగుమతి చెయ్యలేదు.",
"import-error-create": "\"$1\" పేజీని సృష్టించే అనుమతి మీకు లేదు కాబట్టి దాన్ని దిగుమతి చెయ్యలేదు.",
"pageinfo-file-hash": "హ్యాష్ వ్యాల్యూ",
"markaspatrolleddiff": "పరీక్షించినట్లుగా గుర్తు పెట్టు",
"markaspatrolledtext": "ఈ వ్యాసాన్ని పరీక్షించినట్లుగా గుర్తు పెట్టు",
+ "markaspatrolledtext-file": "దస్త్రపు ఈ కూర్పు నిఘాలో ఉందని గుర్తు పెట్టు",
"markedaspatrolled": "పరీక్షింపబడినట్లు గుర్తింపబడింది",
"markedaspatrolledtext": "[[:$1]] యొక్క ఎంచుకున్న కూర్పుని పరీక్షించినట్లుగా గుర్తించాం.",
"rcpatroldisabled": "ఇటీవలి మార్పుల నిఘాను అశక్తం చేసాం",
"markedaspatrollederrortext": "నిఘాలో ఉన్నట్లు గుర్తించేందుకుగాను, కూర్పును చూపించాలి.",
"markedaspatrollederror-noautopatrol": "మీరు చేసిన మార్పులను మీరే నిఘాలో పెట్టలేరు.",
"markedaspatrollednotify": "$1 లో చేసిన ఈ మార్పు పర్యవేక్షణలో ఉన్నట్టుగా గుర్తించబడింది.",
+ "markedaspatrollederrornotify": "నిఘాలో ఉన్నట్టుగా గుర్తించడం విఫలమైంది.",
"patrol-log-page": "నిఘా చిట్టా",
"patrol-log-header": "ఇది పర్యవేక్షించిన కూర్పుల చిట్టా.",
"confirm-markpatrolled-button": "సరే",
+ "confirm-markpatrolled-top": "$2 యొక్క కూర్పు $3 నిఘాలో ఉన్నట్టుగా గుర్తు పెట్టాలా?",
"deletedrevision": "పాత సంచిక $1 తొలగించబడినది.",
"filedeleteerror-short": "ఫైలు తొలగించడంలో పొరపాటు: $1",
"filedeleteerror-long": "ఫైలుని తొలగించడంలో పొరపాట్లు జరిగాయి:\n\n$1",
"newimages-user": "ఐపీ చిరునామా లేదా వాడుకరి పేరు",
"newimages-newbies": "కొత్త ఖాతాల రచనలని మాత్రమే చూపించు",
"newimages-showbots": "బాట్లు చేసిన అప్లోడ్లు చూపించు",
+ "newimages-hidepatrolled": "నిఘాలో ఉన్న ఎక్కింపులను దాచు",
"newimages-mediatype": "మాధ్యమ రకం:",
"noimages": "చూసేందుకు ఏమీ లేదు.",
"ilsubmit": "వెతుకు",
"limitreport-expensivefunctioncount": "ఖరీదైన పార్సర్ ఫంక్షన్ల సంఖ్య",
"limitreport-unstrip-size-value": "$1/$2 {{PLURAL:$2|బైటు|బైట్లు}}",
"expandtemplates": "మూసలను విస్తరించు",
- "expand_templates_intro": "à°\88 à°ªà±\8dà°°à°¤à±\8dà°¯à±\87à°\95 à°ªà±\87à°\9cà±\80 à°®à±\80à°°à°¿à°\9aà±\8dà°\9aà°¿à°¨ à°®à±\82సలనà±\81 à°ªà±\82à°°à±\8dతిà°\97à°¾ విసà±\8dతరిà°\82à°\9aà°¿, చూపిస్తుంది. ఇది <code><nowiki>{{</nowiki>#language:...}}</code> వంటి పార్సరు ఫంక్షన్లను, <code><nowiki>{{</nowiki>CURRENTDAY}}</code> వంటి చరరాశులను (వేరియబుల్) కూడా విస్తరిస్తుంది. \nనిజానికి ఇది మీసాల బ్రాకెట్లలో ఉన్న ప్రతీదాన్నీ విస్తరిస్తుంది.",
+ "expand_templates_intro": "à°\88 à°ªà±\8dà°°à°¤à±\8dà°¯à±\87à°\95 à°ªà±\87à°\9cà±\80 విà°\95à±\80à°\9fà±\86à°\95à±\8dà°¸à±\8dà°\9fà±\81à°¨à±\81 à°¤à±\80à°¸à±\81à°\95à±\81ని à°\85à°\82à°¦à±\81à°²à±\8b à°\89à°¨à±\8dà°¨ à°®à±\82సలనà±\8dనిà°\9fà°¿à°¨à±\80 విసà±\8dతరిà°\82à°\9aà°¿ చూపిస్తుంది. ఇది <code><nowiki>{{</nowiki>#language:...}}</code> వంటి పార్సరు ఫంక్షన్లను, <code><nowiki>{{</nowiki>CURRENTDAY}}</code> వంటి చరరాశులను (వేరియబుల్) కూడా విస్తరిస్తుంది. \nనిజానికి ఇది మీసాల బ్రాకెట్లలో ఉన్న ప్రతీదాన్నీ విస్తరిస్తుంది.",
"expand_templates_title": "{{FULLPAGENAME}} మొదలగు వాటి కొరకు సందర్భ శీర్షిక:",
- "expand_templates_input": "విసà±\8dతరిà°\82à°\9aవలసిన పాఠà±\8dà°¯à°\82:",
+ "expand_templates_input": "à°\87à°¨à±\8dâ\80\8cà°ªà±\81à°\9fà±\8d విà°\95à±\80à°\9fà±\86à°\95à±\8dà°¸à±\8dà°\9fà±\8d:",
"expand_templates_output": "ఫలితం",
"expand_templates_xml_output": "XML ఔట్‌పుట్",
"expand_templates_html_output": "ముడి HTML ఔట్పుట్",
"log-action-filter-managetags-deactivate": "ట్యాగు అచేతనం",
"log-action-filter-protect-protect": "సంరక్షణ",
"log-action-filter-upload-upload": "కొత్త ఎక్కింపు",
+ "authmanager-create-disabled": "ఖాతా సృష్టించడాన్ని అశక్తం చేసాం.",
+ "authmanager-create-from-login": "ఖాతా సృష్టించడానికి, ఫీల్డులను నింపండి.",
+ "authmanager-create-not-in-progress": "ఖాతా సృష్టించే పని జరగడం లేదు. లేదా సెషను డేటా పోయింది. మళ్ళీ మొదటినుండి మొదలుపెట్టండి.",
+ "authmanager-create-no-primary": "మీరిచ్చిన విశేషాలతో ఖాతాను సృష్టించలేకపోయాం.",
+ "authmanager-link-no-primary": "మీరిచ్చిన విశేషాలతో ఖాతాలను లింకు చెయ్యలేకపోయాం.",
+ "authmanager-link-not-in-progress": "ఖాతాలను లింకు చేసే పని జరగడం లేదు. లేదా సెషను డేటా పోయింది. మళ్ళీ మొదటినుండి మొదలుపెట్టండి.",
+ "authmanager-authplugin-setpass-failed-title": "సంకేతపదం మార్పు విఫలమైంది.",
+ "authmanager-authplugin-setpass-failed-message": "సంకేతపదం మార్పును ఆథెంటికేషన్ ప్లగిన్ తిరస్కరించింది.",
+ "authmanager-authplugin-create-fail": "ఖాతా సృష్టిని ఆథెంటికేషన్ ప్లగిన్ తిరస్కరించింది.",
+ "authmanager-authplugin-setpass-denied": "ఆథెంటికేషన్ ప్లగిన్ సంకేతపదం మార్పులను అనుమతించదు.",
+ "authmanager-authplugin-setpass-bad-domain": "తప్పు డొమెయిన్",
+ "authmanager-autocreate-noperm": "ఆటోమాటిక్ ఖాతా సృష్టికి అనుమతి లేదు.",
"authmanager-userdoesnotexist": "వాడుకరి ఖాతా \"$1\" నమోదయి లేదు.",
"authmanager-userlogin-remembermypassword-help": "సెషను ముగిసిన తరువాత కూడా సంకేతపదాన్ని గుర్తుంచుకోమంటారా",
"authmanager-username-help": "ధ్రువీకరణ కోసం వాడుకరిపేరు.",
"group-autoconfirmed": "Otomatik onaylanmış kullanıcılar",
"group-bot": "Botlar",
"group-sysop": "Hizmetliler",
- "group-interface-admin": "Arayüz yöneticisi",
+ "group-interface-admin": "Arayüz yöneticileri",
"group-bureaucrat": "Bürokratlar",
"group-suppress": "Gözetmenler",
"group-all": "(hepsi)",
"right-reupload-shared": "Paylaşılan ortam deposundaki dosyaları yerel olarak geçersiz kıl",
"right-upload_by_url": "Bir URL adresinden dosya yükle",
"right-purge": "Doğrulama yapmadan bir sayfa için site belleğini temizle",
- "right-autoconfirmed": "IP-tabanlı hız limitleri etkilenmeyecektir",
+ "right-autoconfirmed": "IP-tabanlı hız limitleri etkilenme",
"right-bot": "Otomatik bir işlem gibi muamele gör",
"right-nominornewtalk": "Kullanıcı tartışma sayfalarında yaptığı küçük değişiklikler kullanıcıya yeni mesaj bildirimiyle bildirilmez",
"right-apihighlimits": "API sorgularında yüksek sınır kullan",
"right-deletedtext": "Silinmiş metni ve silinmiş revizyonlar arasındaki değişiklikleri gör",
"right-browsearchive": "Silinen sayfaları ara",
"right-undelete": "Bir sayfanın silinmesini geri al",
- "right-suppressrevision": "Sysoplardan gizlenmiş revizyonlarını gizle ve göster",
+ "right-suppressrevision": "Hizmetlilerden revizyon gizle ve geri getir",
"right-viewsuppressed": "Herhangi bir kullanıcıdan saklanan sürümleri göster",
"right-suppressionlog": "Özel günlükleri gör",
"right-block": "Diğer kullanıcıların değişiklik yapmalarını engelle",
"right-blockemail": "Bir kullanıcının e-posta göndermesini engelle",
- "right-hideuser": "Bir kullanıcı adını engelle, genelden gizleyerek",
+ "right-hideuser": "Herkesden gizleyerek bir kullanıcı adını engelle",
"right-ipblock-exempt": "IP engellemelerini atla, otomatik engelle ve aralık engellemeleri",
"right-unblockself": "Kendi engellemesini kaldır",
"right-protect": "Koruma düzeylerini değiştir ve kademeli korumalı sayfaları düzenle",
"right-editprotected": "\"{{int:protect-level-sysop}}\" olarak korunan sayfalarda değişiklik yap",
"right-editsemiprotected": "\"{{int:protect-level-autoconfirmed}}\" olarak korunan sayfalarda değişiklik yap",
"right-editcontentmodel": "Sayfanın içerik modelini düzenle",
- "right-editinterface": "Kullanıcı arayüzünü değiştirmek",
- "right-editusercss": "Diğer kullanıcıların CSS dosyalarında değişiklik yap",
- "right-edituserjson": "Diğer kullanıcıların JSON dosyalarını düzenle",
- "right-edituserjs": "Diğer kullanıcıların JS dosyalarında değişiklik yap",
+ "right-editinterface": "Kullanıcı arayüzünü değiştir",
+ "right-editusercss": "Diğer kullanıcıların CSS sayfalarında değişiklik yap",
+ "right-edituserjson": "Diğer kullanıcıların JSON sayfalarında değişiklik yap",
+ "right-edituserjs": "Diğer kullanıcıların JS sayfalarında değişiklik yap",
"right-editsitecss": "Sitewide CSS düzenle",
"right-editsitejson": "Sitewide JSON'u düzenle",
"right-editsitejs": "Sitewide JavaScript'i düzenle",
"right-editmywatchlist": "Kendi izleme listeni düzenle. Not, bazı eylemler bu yetki olmadan da sayfa ekleyebilir.",
"right-viewmyprivateinfo": "Kendi özel bilgilerini görüntüle (e-posta adresi, gerçek isim vb.)",
"right-editmyprivateinfo": "Kendi özel bilgilerini değiştir (e-posta adresi, gerçek isim vb.)",
- "right-editmyoptions": "tercihlerini düzenle",
+ "right-editmyoptions": "Tercihlerini düzenle",
"right-rollback": "Belirli bir sayfayı değiştiren son kullanıcının değişikliklerini hızlıca geri döndür",
"right-markbotedits": "Geri döndürülen değişiklikleri, bot değişiklikleri olarak işaretle",
"right-noratelimit": "Derecelendirme sınırlamalarından etkilenme",
"filehist-filesize": "Dosya boyutu",
"filehist-comment": "Açıklama",
"imagelinks": "Dosya kullanımı",
- "linkstoimage": "Bu görüntü dosyasına bağlantısı olan {{PLURAL:$1|sayfa|$1 sayfa}}:",
- "linkstoimage-more": "$1'den fazla {{PLURAL:$1|sayfa|sayfa}} bu dosyaya bağlantı veriyor.\nSıradaki liste sadece bu dosyaya bağlantı veren {{PLURAL:$1|ilk dosyayı|ilk $1 dosyayı}} gösteriyor.\n[[Special:WhatLinksHere/$2|Tam bir liste]] mevcuttur.",
- "nolinkstoimage": "Bu dosyaya bağlantı veren bir sayfa yok.",
+ "linkstoimage": "Aşağıdaki {{PLURAL:$1|sayfa|$1 sayfa}} bu dosyayı kullanmaktadır:",
+ "linkstoimage-more": "$1 {{PLURAL:$1|sayfadan|sayfadan}} fazlası bu dosyayı kullanıyor.\nAşağıdaki listede sadece bu dosyayı kullanan {{PLURAL:$1|ilk sayfa|ilk $1 sayfa}} gösterilmektedir.\n[[Special:WhatLinksHere/$2|Tam listesi]] mevcuttur.",
+ "nolinkstoimage": "Bu dosyayı kullanan sayfa yok.",
"morelinkstoimage": "Bu dosyaya [[Special:WhatLinksHere/$1|daha fazla bağlantıları]] gör.",
"linkstoimage-redirect": "$1 (dosya yönlendirme) $2",
"duplicatesoffile": "Şu {{PLURAL:$1|dosya|$1 dosya}}, bu dosyanın kopyası ([[Special:FileDuplicateSearch/$2|daha fazla ayrıntı]]):",
"mimesearch": "MIME araması",
"mimesearch-summary": "Bu sayfa, dosyaların MIME türlerine göre filtrelenmesini sağlar. Girdi: içerik_türü/alt_tür veya içerik_türü/*, örn. <code>image/jpeg</code>.",
"mimetype": "MIME türü:",
- "download": "yükle",
+ "download": "indir",
"unwatchedpages": "İzlenmeyen sayfalar",
"listredirects": "Yönlendirmeleri listele",
"listduplicatedfiles": "Kopyası bulunan dosyalar listesi",
"log": "Günlükler",
"logeventslist-submit": "Göster",
"logeventslist-more-filters": "Daha fazla süzgeç:",
+ "logeventslist-patrol-log": "Devriye günlüğü",
+ "logeventslist-tag-log": "Etiket günlüğü",
"all-logs-page": "Tüm genel günlükler",
"alllogstext": "{{SITENAME}} için mevcut tüm günlüklerin birleşik gösterimi.\nGünlük tipini, kullanıcı adını (büyük-küçük harf duyarlı), ya da etkilenen sayfayı (yine büyük-küçük harf duyarlı) seçerek görünümü daraltabilirsiniz.",
"logempty": "Kayıtlarda eşleşen bilgi yok.",
"dellogpage": "Silme günlüğü",
"dellogpagetext": "Aşağıda en son silme işlemlerinin bir listesi bulunmaktadır.",
"deletionlog": "silme günlüğü",
+ "logentry-create-create": "$1, $3 adlı sayfayı {{GENDER:$2|oluşturdu}}",
"reverted": "Önceki sürüm geri getirildi",
"deletecomment": "Neden:",
"deleteotherreason": "Diğer/ilave neden:",
"rcfilters-invalid-filter": "Яраксыз фильтр",
"rcfilters-filterlist-title": "Фильтрлар",
"rcfilters-filterlist-feedbacklink": "Әлеге фильтрлау кораллары турында турында фикер калдырыгыз",
+ "rcfilters-highlightmenu-title": "Төсен сайлагыз",
+ "rcfilters-highlightmenu-help": "Үзлекләрен аеру өчен аның төсен сайлагыз",
"rcfilters-filtergroup-authorship": "Үзгәртүләрнең авторлыгы",
"rcfilters-filter-editsbyself-label": "Сезнең үзгәртүләр",
"rcfilters-filter-editsbyself-description": "Сезнең кертемегез.",
"rcfilters-liveupdates-button": "Автоматик яңарту",
"rcfilters-watchlist-markseen-button": "Бар үзгәртүләрне каралган дип билгеләргә",
"rcfilters-watchlist-edit-watchlist-button": "Күзәтү исемлегегезне үзгәртү",
+ "rcfilters-watchlist-showupdated": "Сезнең соңгы төзәтмәләрдән соң үзгәргән битләр <strong>калын</strong> һәм тулы маркер белән күрсәтелгән",
"rcfilters-preference-label": "«Соңгы үзгәртүләр» битенең яңа юрамасын яшерү",
"rcnotefrom": "Астарак <strong>$3, $4</strong> өчен {{PLURAL:$5|үзгәртүләр күрсәтелгән}} (<strong>$1</strong> артык түгел).",
"rclistfrom": "$3 $2 башлап яңа үзгәртүләрне күрсәт",
"compare-rev1": "Беренче юрама",
"compare-rev2": "Икенче юрама",
"compare-submit": "Чагыштыр",
+ "permanentlink": "Даими сылтама",
"dberr-problems": "Гафу итегез! Сайтта техник кыенлыклар чыкты.",
"dberr-again": "Сәхифәне берничә минуттан соң яңартып карагыз.",
"dberr-info": "(Мәгълүматлар базасы серверы белән тоташырга мөмкин түгел: $1)",
"move-page-legend": "منتقلئ صفحہ",
"movepagetext": "درج ذیل فارم کے ذریعہ صفحہ کو نیا نام دیا جاسکتا ہے، اس کے ساتھ صفحہ کا تاریخچہ بھی منتقل ہو جائے گا اور نئے عنوان کے جانب قدیم عنوان کو رجوع مکرر کردیا جائے گا۔\n\nاس بات کا یقین کر لیں کہ [[Special:DoubleRedirects|دوہرے]] یا [[Special:BrokenRedirects|شکستہ رجوع مکررات]] موجود نہ ہوں۔\n\nنیز آپ اس بات کے بھی ذمہ دار ہیں کہ روابط انہیں جگہوں سے مربوط رہیں جہاں سے ابھی ہیں۔\n\nخیال رہے کہ اگر نئے عنوان سے کوئی صفحہ پہلے سے موجود ہو تو یہ صفحہ منتقل '''نہیں''' ہوگا، ہاں اگر صفحہ خالی ہو اور اس کا گذشتہ ترمیمی تاریخچہ موجود نہ ہو تو منتقل کیا جا سکتا ہے۔\nاس کا مطلب یہ ہے کہ آپ سے اگر غلطی ہوجائے تو آپ صفحہ کو اسی جگہ لوٹا سکتے ہیں، تاہم موجود صفحہ پر برتحریر (overwrite) نہیں کرسکتے۔\n\n<strong>اطلاع:</strong>\nکسی اہم اور مقبول صفحہ کی منتقلی، غیرمتوقع اور پریشان کن بھی ہی ہوسکتی ہے اس لیے منتقلی سے قبل یقین کرلیں کہ آپ اس کے منطقی نتائج سے باخبر ہیں۔",
"movepagetext-noredirectfixer": "درج ذیل فارم کے ذریعہ صفحہ کو نیا نام دیا جاسکتا ہے، اس کے ساتھ صفحہ کا تاریخچہ بھی منتقل ہو جائے گا اور نئے عنوان کے جانب قدیم عنوان کو رجوع مکرر کردیا جائے گا۔\n\nاس بات کا یقین کر لیں کہ [[Special:DoubleRedirects|دوہرے]] یا [[Special:BrokenRedirects|شکستہ رجوع مکررات]] موجود نہ ہوں۔\n\nنیز آپ اس بات کے بھی ذمہ دار ہیں کہ روابط انہیں جگہوں سے مربوط رہیں جہاں سے ابھی ہیں۔\n\nخیال رہے کہ اگر نئے عنوان سے کوئی صفحہ پہلے سے موجود ہو تو یہ صفحہ منتقل '''نہیں''' ہوگا، ہاں اگر صفحہ خالی ہو اور اس کا گذشتہ ترمیمی تاریخچہ موجود نہ ہو تو منتقل کیا جا سکتا ہے۔\nاس کا مطلب یہ ہے کہ آپ سے اگر غلطی ہوجائے تو آپ صفحہ کو اسی جگہ لوٹا سکتے ہیں، تاہم موجود صفحہ پر برتحریر (overwrite) نہیں کرسکتے۔\n\n<strong>اطلاع:</strong>\nکسی اہم اور مقبول صفحہ کی منتقلی، غیرمتوقع اور پریشان کن بھی ہی ہوسکتی ہے اس لیے منتقلی سے قبل یقین کرلیں کہ آپ اس کے منطقی نتائج سے باخبر ہیں۔",
- "movepagetalktext": "اگر آپ اس خانے کو نشان زد کریں تو ملحقہ تبادلہ خیال صفحہ بھی نئے عنوان کی جانب خودکار طور پر منتقل ہو جائے گا اگر اس عنوان کے تحت پہلے سے کوئی تبادلۂ خیال صفحہ موجود نہ ہو۔\n\nاس صورت میں آپ کو دستی طور پر اس صفحہ کو منتقل ضم کرنا ہوگا۔",
+ "movepagetalktext": "اگر آپ اس خانے کو نشان زد کریں تو ملحقہ تبادلہ خیال صفحہ بھی (بشرطیکہ موجود ہو) نئے عنوان کی جانب خودکار طور پر منتقل ہو جائے گا۔\n\nاگر آپ نے اس خانہ کو نشان زد نہیں کیا تو ملحقہ تبادلہ خیال صفحہ کو دستی طور پر منتقل کرکے ضم کرنا ہوگا۔",
"moveuserpage-warning": "<strong>انتباہ:</strong> آپ صارف صفحہ کو منتقل کر رہے ہیں۔ واضح رہے کہ اس منتقلی کے بعد صارف کا محض صفحہ منتقل ہوگا، اس کا صارف نام تبدیل <em>نہیں</em> ہوگا۔",
"movecategorypage-warning": "<strong>انتباہ:</strong> آپ زمرہ منتقل کر رہے ہیں۔ واضح رہے کہ منتقلی کے بعد اس زمرے میں موجود صفحات نئے زمرے میں منتقل <em>نہیں</em> ہونگے۔",
"movenologintext": "صفحہ کو منتقل کرنے کے لیے آپ کو اپنے کھاتے میں [[Special:UserLogin|داخل ہونا]] ضروری ہے۔",
"toc": "Mục lục",
"showtoc": "hiện",
"hidetoc": "ẩn",
- "collapsible-collapse": "Thu gọn",
- "collapsible-expand": "Mở rộng",
+ "collapsible-collapse": "Ẩn",
+ "collapsible-expand": "Hiện",
"confirmable-confirm": "{{GENDER:$1}}Bạn chắc chứ?",
"confirmable-yes": "Có",
"confirmable-no": "Không",
"ns-specialprotected": "מען קען נישט רעדאגירן ספעציעלע בלעטער.",
"titleprotected": "דער טיטל איז געשיצט פון ווערן געשאפֿן דורך [[User:$1|$1]].\nדי אורזאך איז <em>$2</em>.",
"filereadonlyerror": "נישט מעגלעך צו ענדערן די טעקע \"$1\" ווייל די טעקע רעפאזיטאריום \"$2\" איז אין נאר־ליינען מצב.\n\nדער סיסאפ וואס האט זי פארשפארט האט געגעבן דעם הסבר: \"$3\"",
+ "invalidtitle": "אומגילטיקער טיטל",
"invalidtitle-knownnamespace": "אומגילטירער טיטל מיט נאמענטייל \"$2\" און טעקסט \"$3\"",
"invalidtitle-unknownnamespace": "אומגילטיקער טיטל מיט אומבאוואוסטן נאמענטייל נומער $1 און טעקסט \"$2\"",
"exception-nologin": "נישט אַרײַנלאגירט",
"diff-multi-manyusers": "({{PLURAL:$1|איין מיטלסטע ווערסיע |$1 מיטלסטע ווערסיעס}} פֿון מער ווי {{PLURAL:$2|איין באַניצער|$2 באַניצער}} נישט געוויזן.)",
"difference-missing-revision": "{{PLURAL:$2|איין ווערסיע|$2 ווערסיעס}} פון דעם דיפערענץ ($1) {{PLURAL:$2|האט}} מען נישט געטראפן.\n\nדאס געשעט געוויינלעך פון פאלגן א פארעלטערטן היסטאריע לינק צו א בלאט וואס איז געווארן אויסגעמעקט.\nפרטים קען מען געפינען אינעם [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} אויסמעקונג לאגבוך].",
"searchresults": "זוכן רעזולטאטן",
+ "search-filter-title-prefix-reset": "זוכן אלע בלעטער",
"searchresults-title": "זוכן רעזולטאַטן פֿאַר \"$1\"",
"titlematches": "בלאט קעפל שטימט",
"textmatches": "בלעטער מיט פאַסנדיקן אינהאַלט",
"grant-createaccount": "שאַפֿן קאנטעס",
"grant-createeditmovepage": "שאפֿן, רעדאקטירן און באוועגן בלעטער",
"grant-delete": "אויסמעקן בלעטער, ווערסיעס און לאגבוך פרטים",
- "grant-editinterface": "רע×\93×\90ק×\98×\99ר×\9f ×\93×¢×\9d ×\9e×¢×\93×\99×¢×\95×\95×\99ק×\99 × ×\90×\9e×¢× ×\98×\99×\99×\9c ×\90×\95×\9f ×\91×\90× ×\99צער CSS/JSON/JavaScript",
+ "grant-editinterface": "רע×\93×\90ק×\98×\99ר×\9f ×\93×¢×\9d ×\9e×¢×\93×\99×¢×\95×\95×\99ק×\99 × ×\90×\9e×¢× ×\98×\99×\99×\9c ×\90×\95×\9f ×\95×\95×\99ק×\99×\95×\95×\99×\99×\98/×\91×\90× ×\99צער JSON",
"grant-editmycssjs": "רעדאקטירן אייער באניצער CSS/JSON/JavaScript",
"grant-editmyoptions": "רעדאקטירן אײַערע באניצער פרעפֿערענצן",
"grant-editmywatchlist": "רעדאקטירן אײַער אויפֿפאסונג ליסטע",
"rcfilters-other-review-tools": "אנדערע רעצענזיע ווערקצייג",
"rcfilters-group-results-by-page": "גרופירן רעזולטאטן לויט בלאט",
"rcfilters-activefilters": "אַקטיווע פילטערס",
+ "rcfilters-activefilters-hide": "באַהאַלטן",
+ "rcfilters-activefilters-show": "ווייזן",
"rcfilters-advancedfilters": "פֿארגעשריטענע פֿילטערס",
"rcfilters-limit-title": "רעזולטאטן צו ווייזן",
"rcfilters-days-title": "לעצטיקע טעג",
"filehist-comment": "באמערקונג",
"imagelinks": "טעקע באַניץ",
"linkstoimage": "{{PLURAL:$1|דער פאלגנדער בלאט ניצט|די פאלגנדע בלעטער ניצן}} דאס דאזיגע בילד:",
- "linkstoimage-more": "×\9eער ×\95×\95×\99 $1 {{PLURAL:$1|×\91×\9c×\90Ö·×\98 פֿ×\90ַר×\91×\99× ×\93×\98|×\91×\9c×¢×\98ער פֿ×\90ַר×\91×\99× ×\93×\9f}} צ×\95 ×\93ער ×\93×\90×\96×\99×\92ער ×\98עקע.\n×\93×\99 פֿ×\90×\9c×\92× ×\93×¢ ×\9c×\99ס×\98×¢ ×\95×\95ײַ×\96×\98 {{PLURAL:$1|×\93×¢×\9d ערש×\98×\9f ×\91×\9c×\90Ö·×\98 ×\9c×\99× ×§|×\93×\99 ערש×\98×¢ $1 ×\91×\9c×\90Ö·×\98 ×\9c×\99× ×§×¢×\9f}} צ×\95 ×\93ער ×\98עקע.\nס'×\90×\99×\96 פֿ×\90ַר×\90Ö·×\9f[[Special:WhatLinksHere/$2|פֿולע רשימה]].",
+ "linkstoimage-more": "×\9eער ×\95×\95×\99 $1 {{PLURAL:$1|×\91×\9c×\90Ö·×\98 × ×\99צ×\98|×\91×\9c×¢×\98ער × ×\99צ×\9f}} ×\93×\99 ×\93×\90×\96×\99×\92×¢ ×\98עקע.\n×\93×\99 פֿ×\90×\9c×\92× ×\93×¢ ×\9c×\99ס×\98×¢ ×\95×\95ײַ×\96×\98 × ×\90ר {{PLURAL:$1|×\93×¢×\9d ערש×\98×\9f ×\91×\9c×\90Ö·×\98 ×\95×\95×\90ס × ×\99צ×\98|×\93×\99 ערש×\98×¢ $1 ×\91×\9c×¢×\98ער ×\95×\95×\90ס × ×\99צ×\9f}} ×\93×\99 ×\98עקע.\nס'×\90×\99×\96 פֿ×\90ַר×\90Ö·×\9f ×\90 [[Special:WhatLinksHere/$2|פֿולע רשימה]].",
"nolinkstoimage": "נישטא קיין בלעטער וואס פארבינדן צו די טעקע.",
"morelinkstoimage": "באַקוקן [[Special:WhatLinksHere/$1|מער לינקען]] צו דער טעקע.",
"linkstoimage-redirect": "$1 (טעקע ווײַטערפֿירונג) $2",
"action-delete": "删除本页",
"action-deleterevision": "删除修订",
"action-deletelogentry": "删除日志记录",
- "action-deletedhistory": "查看页面被删除的历史",
+ "action-deletedhistory": "查看已被删除的页面历史",
"action-deletedtext": "查看已删除的修订版本文字",
"action-browsearchive": "搜索已被删除的页面",
"action-undelete": "还原页面",
"broken-file-category": "檔案連結損壞的頁面",
"about": "關於",
"article": "內容頁面",
- "newwindow": "(以新視窗開啟)",
+ "newwindow": "(以新視窗開啟)",
"cancel": "取消",
"moredotdotdot": "更多...",
"morenotlisted": "這可能只是部份清單。",
"specialpage": "特殊頁面",
"personaltools": "個人工具",
"talk": "討論",
- "views": "檢è¦\96",
+ "views": "è¦\96å\9c\96",
"toolbox": "工具",
"tool-link-userrights": "變更{{GENDER:$1|使用者}}群組",
"tool-link-userrights-readonly": "檢視{{GENDER:$1|使用者}}群組",
"versionrequiredtext": "需使用 $1 版本的 MediaWiki 才能使用此頁面。\n請參考 [[Special:Version|版本]]。",
"ok": "確定",
"retrievedfrom": "取自 \"$1\"",
- "youhavenewmessages": "您有 $1 ($2)。",
+ "youhavenewmessages": "{{PLURAL:$3|您有}}$1($2)。",
"youhavenewmessagesfromusers": "{{PLURAL:$4|您}}有來自{{PLURAL:$3|另一位使用者|$3 位使用者}}的 $1 ($2)。",
"youhavenewmessagesmanyusers": "你有來自多位使用者的 $1 ($2)。",
"newmessageslinkplural": "{{PLURAL:$1|一則新訊息|999=新訊息}}",
"loginsuccesstitle": "已登入",
"loginsuccess": "<strong>{{GENDER:|您|妳|你}}現在已經以 \"$1\" 的身分登入了 {{SITENAME}}。</strong>",
"nosuchuser": "查無名稱為 \"$1\" 的使用者。\n使用者名稱有大小寫區分,\n請檢查您拼寫是否正確,或者 [[Special:CreateAccount|建立新帳號]]。",
- "nosuchusershort": "查無使用者 \"$1\",\n請檢查您拼寫是否正確。",
+ "nosuchusershort": "查無使用者「$1」,請檢查您拼寫是否正確。",
"nouserspecified": "您必須指定一個使用者名稱。",
"login-userblocked": "這位使用者已被封鎖,不允許登入。",
"wrongpassword": "您輸入的使用者名稱或密碼錯誤,請再試一次。",
"link_sample": "連結標題",
"link_tip": "內部連結",
"extlink_sample": "http://www.example.com 連結標題",
- "extlink_tip": "外部連結 (記得以 http:// 開頭)",
- "headline_sample": "第 1 層標題文字",
- "headline_tip": "第 2 層標題文字",
+ "extlink_tip": "外部連結(記得以 http:// 開頭)",
+ "headline_sample": "標題文字",
+ "headline_tip": "2級標題",
"nowiki_sample": "插入非格式化文字",
"nowiki_tip": "忽略 Wiki 格式化語法",
"image_sample": "範例.jpg",
"media_sample": "範例.ogg",
"media_tip": "檔案連結",
"sig_tip": "您的簽名與日期時間",
- "hr_tip": "水平線 (少用)",
+ "hr_tip": "水平線(謹慎使用)",
"summary": "摘要:",
"subject": "主旨:",
"minoredit": "這是一個次要修訂",
"unicode-support-fail": "看起來您的瀏覽器不支援Unicode。需要Unicode才能編輯頁面,所以您的編輯無法儲存。",
"yourdiff": "差異",
"copyrightwarning": "請注意,所有於 {{SITENAME}} 所做的貢獻會依據 $2 授權條款發佈 (詳情請見 $1)。\n若您不希望您的著作被任意修改與散佈,請勿在此發表文章。<br />\n您同時向我們保証在此的著作內容是您自行撰寫,或是取自不受版權保護的公開領域或自由資源。\n<strong>請勿在未經授權的情況下發表文章!</strong>",
- "copyrightwarning2": "請注意,所有於 {{SITENAME}} 所做的貢獻可能會被其他貢獻者編輯,修改或刪除。\n若您不希望您的著作被任意修改與散佈,請勿在此發表文章。<br />\n您同時向我們保証在此的著作內容是您自行撰寫,或是取自不受版權保護的公開領域或自由資源 (詳情請見 $1)。\n<strong>請勿在未經授權的情況下發表文章!</strong>",
+ "copyrightwarning2": "請注意,所有於{{SITENAME}}所做的貢獻可能會被其他貢獻者編輯,修改或刪除。\n若您不希望您的著作被任意修改與散佈,請勿在此發表文章。<br />\n您同時向我們保証在此的著作內容是您自行撰寫,或是取自不受版權保護的公開領域或自由資源(詳情請見 $1)。\n<strong>請勿在未經授權的情況下發表文章!</strong>",
"editpage-cannot-use-custom-model": "此頁面的內容模型不能被修改。",
"longpageerror": "<strong>錯誤:您所送出的文字內容共有 {{PLURAL:$1|1 KB|$1 KB}},已超出系統上限 {{PLURAL:$2|1 KB|$2 KB}}。</strong>\n\n無法儲存。",
"readonlywarning": "<strong>警告:資料庫已被鎖定以進行維護,因此無法儲存您目前所做的編輯動作。</strong>\n您可先複製您的文字並貼上到文字檔案中儲存,稍後再儲存您編輯。\n\n鎖定資料庫的系統管理員有以下說明:$1",
"history-feed-item-nocomment": "$1 於 $2",
"history-feed-empty": "請求的頁面不存在,\n可能已被刪除或重新命名。\n請嘗試 [[Special:Search|搜尋本站]] 取得其他相關的新頁面。",
"history-edit-tags": "編輯已選擇修訂的標籤",
- "rev-deleted-comment": "(已移除編輯摘要)",
+ "rev-deleted-comment": "(已移除編輯摘要)",
"rev-deleted-user": " (已移除使用者名稱)",
"rev-deleted-event": "(已移除日誌明細)",
"rev-deleted-user-contribs": "[使用者名稱或 IP 位址已移除 - 已隱藏貢獻清單中的編輯]",
"youremail": "Email:",
"username": "{{GENDER:$1|使用者名稱}}:",
"prefs-memberingroups": "{{GENDER:$2|所屬}}{{PLURAL:$1|群組}}:",
- "group-membership-link-with-expiry": "$1 (直到 $2)",
+ "group-membership-link-with-expiry": "$1(直到 $2)",
"prefs-registration": "註冊時間:",
"yourrealname": "真實姓名:",
"yourlanguage": "語言:",
"right-editmyuserjs": "編輯自己的使用者 JavaScript 檔",
"right-viewmywatchlist": "檢視自己的監視清單",
"right-editmywatchlist": "編輯自己的監視清單。注意,即使無此權限,某些操作仍會新增頁面至監視清單。",
- "right-viewmyprivateinfo": "檢視自己的私隱資料 (如:電子郵件地址及真實姓名)",
+ "right-viewmyprivateinfo": "檢視自己的私隱資料(如:電子郵件地址及真實姓名)",
"right-editmyprivateinfo": "編輯自己的隱私資料 (如:電子郵件地址及真實姓名)",
"right-editmyoptions": "編輯自己的偏好設定",
"right-rollback": "快速還原最後一位使用者對某一頁面的編輯",
"action-delete": "刪除此頁面",
"action-deleterevision": "刪除修訂",
"action-deletelogentry": "刪除日誌項目",
- "action-deletedhistory": "檢視頁面的刪除歷史",
+ "action-deletedhistory": "檢視已被刪除的頁面歷史",
"action-deletedtext": "查看已刪除的修訂版本文字",
"action-browsearchive": "搜尋已刪除頁面",
"action-undelete": "取消刪除頁面",
"licenses-edit": "編輯授權條款選項",
"license-nopreview": "(不可預覽)",
"upload_source_url": "(您選擇的檔案來自有效、可公開存取的 URL)",
- "upload_source_file": "(您在您的電腦上選擇的檔案)",
+ "upload_source_file": "(您在您的電腦上選擇的檔案)",
"listfiles-delete": "刪除",
"listfiles-summary": "此特殊頁面顯示所有已上傳的檔案。",
"listfiles_search_for": "搜尋媒體名稱:",
"listusersfrom": "顯示使用者開始自:",
"listusers-submit": "顯示",
"listusers-noresult": "查無使用者。",
- "listusers-blocked": "(已封鎖)",
+ "listusers-blocked": "(已封鎖)",
"activeusers": "活動的使用者清單",
"activeusers-intro": "此清單為最近 $1 天有活動的使用者。",
"activeusers-count": "最近 $3 天內有 $1 次動作",
"changed": "變更",
"deletepage": "刪除頁面",
"confirm": "確認",
- "excontent": "內容為:\"$1\"",
+ "excontent": "內容為:「$1」",
"excontentauthor": "內容為:\"$1\",且僅有一位貢獻者 \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|對話]])",
"exbeforeblank": "被清空前的內容為:\"$1\"",
"delete-confirm": "刪除 \"$1\"",
"protectlogtext": "以下為變更頁面保護的清單。\n請參考 [[Special:ProtectedPages|受保護頁面清單]] 檢視目前受保護頁面。",
"protectedarticle": "已保護 \"[[$1]]\"",
"modifiedarticleprotection": "已變更 \"[[$1]]\" 的保護層級",
- "unprotectedarticle": "已解除 \"[[$1]]\" 的保護",
+ "unprotectedarticle": "已解除「[[$1]]」的保護",
"movedarticleprotection": "已移動 \"[[$2]]\" 的保護設定至 \"[[$1]]\"",
"protectedarticle-comment": "{{GENDER:$2|受保護}} \"[[$1]]\"",
"modifiedarticleprotection-comment": "{{GENDER:$2|已變更}} \"[[$1]]\" 的保護層級",
"protect-expiring-local": "期限至 $1",
"protect-expiry-indefinite": "無限期",
"protect-cascade": "保護本頁中包含的頁面 (連鎖保護)",
- "protect-cantedit": "您沒有編輯權限,無法更改此頁面的保護層級。",
+ "protect-cantedit": "您沒有編輯該頁面的權限,因此無法更改頁面的保護層級。",
"protect-othertime": "其它時間:",
"protect-othertime-op": "其它時間",
"protect-existing-expiry": "已設定期限:$2 $3",
"movesubpagetext": "此頁面有 $1 個子頁面如下所示。",
"movesubpagetalktext": "對應的對話頁有以下 $1 頁{{PLURAL:$1|子頁面|子頁面}}。",
"movenosubpage": "此頁面沒有任何子頁面。",
- "movereason": "原因",
+ "movereason": "原因:",
"revertmove": "還原",
"delete_and_move_text": "目標頁面 \"[[:$1]]\" 已存在。\n您是否要刪除該頁面以完成移動?",
"delete_and_move_confirm": "是的,刪除該頁面",
"previousdiff": "← 較舊編輯",
"nextdiff": "較新編輯 →",
"mediawarning": "<strong>警告</strong>:此檔案類型可能包含惡意代碼。\n若執行可能對您的系統造成損害。",
- "imagemaxsize": "圖片大小限制:<br /><em>(用於檔案描述頁面)</em>",
+ "imagemaxsize": "圖片大小限制:<br /><em>(用於檔案描述頁面)</em>",
"thumbsize": "縮圖大小:",
"widthheightpage": "$1 × $2,$3 頁",
"file-info": "檔案大小:$1,MIME 類型:$2",
"exif-exposureprogram-2": "標準模式",
"exif-exposureprogram-3": "光圈優先",
"exif-exposureprogram-4": "快門優先",
- "exif-exposureprogram-5": "藝術程式 (景深優先)",
- "exif-exposureprogram-6": "運動模式 (快速快門優先)",
- "exif-exposureprogram-7": "人像模式 (用於近距離照片,對焦不在背景)",
- "exif-exposureprogram-8": "風景模式 (用於風景照片,對焦在背景)",
+ "exif-exposureprogram-5": "藝術程式(景深優先)",
+ "exif-exposureprogram-6": "運動模式(快速快門優先)",
+ "exif-exposureprogram-7": "人像模式(用於近距離照片,對焦不在背景)",
+ "exif-exposureprogram-8": "風景模式(用於風景照片,對焦在背景)",
"exif-subjectdistance-value": "$1 尺",
"exif-meteringmode-0": "不明",
"exif-meteringmode-1": "平均",
"confirm-unwatch-top": "從您的監視清單中移除此頁面?",
"confirm-rollback-button": "確定",
"confirm-rollback-top": "還原編輯到此頁面?",
+ "confirm-mcrundo-title": "還原變更",
+ "mcrundofailed": "還原失敗",
+ "mcrundo-missingparam": "請求缺少必要參數。",
+ "mcrundo-changed": "自您檢視差異之後,頁面有被變更過。請檢閱新的變更。",
"semicolon-separator": ";",
"comma-separator": "、",
"colon-separator": ":",
"edit-error-long": "錯誤:\n\n$1",
"revid": "修訂 $1",
"pageid": "頁面 ID $1",
- "interfaceadmin-info": "$1\n\n編輯全站 CSS/JS/JSON 檔案的權限,剛剛已從 <code>editinterface</code> 權限裡拆分。若您不清楚為何會收到此錯誤,請查看 [[mw:MediaWiki_1.32/interface-admin]]。",
+ "interfaceadmin-info": "$1\n\n編輯全站 CSS/JS/JSON 檔案的權限,近期已從 <code>editinterface</code> 權限裡拆分。若您不清楚為何會收到此錯誤,請查看 [[mw:MediaWiki_1.32/interface-admin]]。",
"rawhtml-notallowed": "<html> 標籤無法在一般頁面之外使用。",
"gotointerwiki": "離開 {{SITENAME}}",
"gotointerwiki-invalid": "指定的標題無效。",
"specialpage": "特殊頁面",
"personaltools": "個人工具",
"talk": "討論",
- "views": "檢è¦\96",
+ "views": "è¦\96å\9c\96",
"toolbox": "工具",
"jumpto": "跳到:",
"jumptonavigation": "導覽",
*.odl \
*.cs \
*.php \
- *.php5 \
*.inc \
*.m \
*.mm \
*.dox \
*.py \
- *.C \
- *.CC \
- *.C++ \
- *.II \
- *.I++ \
- *.H \
- *.HH \
- *.H++ \
- *.CS \
- *.PHP \
- *.PHP5 \
- *.M \
- *.MM \
- *.PY \
*.txt \
README
RECURSIVE = YES
EXCLUDE_SYMLINKS = YES
EXCLUDE_PATTERNS = LocalSettings.php \
AdminSettings.php \
- StartProfiler.php \
.svn \
*/.git/* \
{{EXCLUDE_PATTERNS}}
$this->output( "Deduplicating ar_rev_id...\n" );
$dbw = $this->getDB( DB_MASTER );
+ PopulateArchiveRevId::checkMysqlAutoIncrementBug( $dbw );
$minId = $dbw->selectField( 'archive', 'MIN(ar_rev_id)', [], __METHOD__ );
$maxId = $dbw->selectField( 'archive', 'MAX(ar_rev_id)', [], __METHOD__ );
"mw",
"mw.Message",
"mw.loader",
- "mw.loader.store",
"mw.html",
"mw.html.Cdata",
"mw.html.Raw",
"classes": [
"mw.log",
"mw.inspect",
- "mw.inspect.reports",
"mw.Debug"
]
}
* @ingroup Maintenance
*/
+use Wikimedia\Rdbms\DBQueryError;
use Wikimedia\Rdbms\IDatabase;
require_once __DIR__ . '/Maintenance.php';
protected function doDBUpdates() {
$this->output( "Populating ar_rev_id...\n" );
$dbw = $this->getDB( DB_MASTER );
+ self::checkMysqlAutoIncrementBug( $dbw );
// Quick exit if there are no rows needing updates.
$any = $dbw->selectField(
}
}
+ /**
+ * Check for (and work around) a MySQL auto-increment bug
+ *
+ * (T202032) MySQL until 8.0 and MariaDB until some version after 10.1.34
+ * don't save the auto-increment value to disk, so on server restart it
+ * might reuse IDs from deleted revisions. We can fix that with an insert
+ * with an explicit rev_id value, if necessary.
+ *
+ * @param IDatabase $dbw
+ */
+ public static function checkMysqlAutoIncrementBug( IDatabase $dbw ) {
+ if ( $dbw->getType() !== 'mysql' ) {
+ return;
+ }
+
+ if ( !self::$dummyRev ) {
+ self::$dummyRev = self::makeDummyRevisionRow( $dbw );
+ }
+
+ $ok = false;
+ while ( !$ok ) {
+ try {
+ $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
+ $dbw->insert( 'revision', self::$dummyRev, $fname );
+ $id = $dbw->insertId();
+ $toDelete[] = $id;
+
+ $maxId = max(
+ (int)$dbw->selectField( 'archive', 'MAX(ar_rev_id)', [], __METHOD__ ),
+ (int)$dbw->selectField( 'slots', 'MAX(slot_revision_id)', [], __METHOD__ )
+ );
+ if ( $id <= $maxId ) {
+ $dbw->insert( 'revision', [ 'rev_id' => $maxId + 1 ] + self::$dummyRev, $fname );
+ $toDelete[] = $maxId + 1;
+ }
+
+ $dbw->delete( 'revision', [ 'rev_id' => $toDelete ], $fname );
+ } );
+ $ok = true;
+ } catch ( DBQueryError $e ) {
+ if ( $e->errno != 1062 ) { // 1062 is "duplicate entry", ignore it and retry
+ throw $e;
+ }
+ }
+ }
+ }
+
/**
* Assign new ar_rev_ids to a set of ar_ids.
* @param IDatabase $dbw
### Format of this file
#
# The top-level keys are module names (as registered in Resources.php).
-# The values of these keys are resource descriptors.
+# Each top-level key holds a resource descriptor that must have one of
+# the following `type` values:
#
-# In each resource descriptor object, the `src` and `integrity` keys are required.
+# - `tar`: For tarball archive (may be gzip-compressed).
+# - `file: For a plain file.
+# - `multi-file`: For multiple plain files.
#
-# * `src`: Full URL to a remote resource.
-# * `integrity`: Cryptographic hash used to verify the remote content.
-# Uses the "integrity metadata" format defined at <https://www.w3.org/TR/SRI/>.
-# * `dest`: An object mapping paths from the remote resource to a destination in
-# `/resources/lib/$module/`. The value may be omitted to indicate that
-# paths should be extracted to the destination directory itself.
+### Type tar
+#
+# The `src` and `integrity` keys are quired.
+#
+# * `src`: Full URL to thes remote resource.
+# * `integrity`: Cryptographic hash (integrity metadata format per <https://www.w3.org/TR/SRI/>).
+# * `dest`: An object mapping paths to files or directory from the remote resource to a destination
+# in the module directory. The value of key in dest may be omitted, which will extract the key
+# directly to the module directory.
+#
+### Type file
+#
+# The `src` and `integrity` keys are quired.
+#
+# * `src`: Full URL to thes remote resource.
+# * `integrity`: Cryptographic hash (integrity metadata format per <https://www.w3.org/TR/SRI/>).
+# * `dest`: The name of the file in the module directory. Default: Basename of URL.
+#
+### Type mult-file
+#
+# The `files` key is required.
+#
+# * `files`: An object mapping destination paths to an object containing `src` and `integrity`
+# keys.
oojs:
+ type: tar
src: https://registry.npmjs.org/oojs/-/oojs-2.2.2.tgz
integrity: sha256-ebgQW2EGrSkBCnDJBGqDpsBDjA3PMN/M8U5DyLHt9mw=
dest:
package/LICENSE-MIT:
package/README.md:
oojs-ui:
+ type: tar
src: https://registry.npmjs.org/oojs-ui/-/oojs-ui-0.28.0.tgz
integrity: sha384-j8bzlCPrfS4sca+U9JO9tdcewDlLlDlOVOsLn+Vqlcg5GU59vLSd7TVm4FiuTowy
dest:
package/dist/History.md:
package/dist/LICENSE-MIT:
package/dist/README.md:
+jquery:
+ type: file
+ src: https://code.jquery.com/jquery-3.2.1.js
+ # From https://code.jquery.com/jquery/
+ integrity: sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE=
+ dest: jquery.js
+qunitjs:
+ type: multi-file
+ files:
+ qunit.js:
+ src: https://code.jquery.com/qunit/qunit-2.6.0.js
+ integrity: sha384-5O3bKbJBbAbxsqV+w/I1fcXgWJgbqM+hmYAPOE9aELSYpcTEsv48X8H+Hnq66V/9
+ qunit.css:
+ src: https://code.jquery.com/qunit/qunit-2.6.0.css
+ integrity: sha384-8vDvsmsuiD7tCQyC+pW2LOwDDgsluGsIPeCqr3rHsDSF2k4WpmfvKKxcgSV5zPai
class ManageForeignResources extends Maintenance {
private $defaultAlgo = 'sha384';
private $tmpParentDir;
+ private $action;
+ private $failAfterOutput = false;
public function __construct() {
global $IP;
This helps developers to download, verify and update local copies of upstream
libraries registered as ResourceLoader modules. See also foreign-resources.yaml.
-For sources that don't publish an integrity hash, leave the value empty at
-first, and run this script with --make-sri to compute the hashes.
+For sources that don't publish an integrity hash, omit "integrity" (or leave empty)
+and run the "make-sri" action to compute the missing hashes.
This script runs in dry mode by default. Use --update to actually change, remove,
or add files to /resources/lib/.
TEXT
);
+ $this->addArg( 'action', 'One of "update", "verify" or "make-sri"', true );
$this->addArg( 'module', 'Name of a single module (Default: all)', false );
- $this->addOption( 'update', ' resources/lib/ missing integrity metadata' );
- $this->addOption( 'make-sri', 'Compute missing integrity metadata' );
- $this->addOption( 'verbose', 'Be verbose' );
+ $this->addOption( 'verbose', 'Be verbose', false, false, 'v' );
// Use a directory in $IP instead of wfTempDir() because
// PHP's rename() does not work across file systems.
public function execute() {
global $IP;
- $module = $this->getArg();
- $makeSRI = $this->hasOption( 'make-sri' );
+ $this->action = $this->getArg( 0 );
+ if ( !in_array( $this->action, [ 'update', 'verify', 'make-sri' ] ) ) {
+ $this->fatalError( "Invalid action argument." );
+ }
$registry = $this->parseBasicYaml(
file_get_contents( __DIR__ . '/foreign-resources.yaml' )
);
+ $module = $this->getArg( 1, 'all' );
foreach ( $registry as $moduleName => $info ) {
- if ( $module !== null && $moduleName !== $module ) {
+ if ( $module !== 'all' && $moduleName !== $module ) {
continue;
}
$this->verbose( "\n### {$moduleName}\n\n" );
+ $destDir = "{$IP}/resources/lib/$moduleName";
- // Validate required keys
- $info += [ 'src' => null, 'integrity' => null, 'dest' => null ];
- if ( $info['src'] === null ) {
- $this->fatalError( "Module '$moduleName' must have a 'src' key." );
+ if ( $this->action === 'update' ) {
+ $this->output( "... updating '{$moduleName}'\n" );
+ $this->verbose( "... emptying /resources/lib/$moduleName\n" );
+ wfRecursiveRemoveDir( $destDir );
+ } elseif ( $this->action === 'verify' ) {
+ $this->output( "... verifying '{$moduleName}'\n" );
+ } else {
+ $this->output( "... checking '{$moduleName}'\n" );
}
- $integrity = is_string( $info['integrity'] ) ? $info['integrity'] : $makeSRI;
- if ( $integrity === false ) {
- $this->fatalError( "Module '$moduleName' must have an 'integrity' key." );
+
+ $this->verbose( "... preparing {$this->tmpParentDir}\n" );
+ wfRecursiveRemoveDir( $this->tmpParentDir );
+ if ( !wfMkdirParents( $this->tmpParentDir ) ) {
+ $this->fatalError( "Unable to create {$this->tmpParentDir}" );
}
- // Download the resource
- $data = Http::get( $info['src'], [ 'followRedirects' => false ] );
- if ( $data === false ) {
- $this->fatalError( "Failed to download resource for '$moduleName'." );
+ if ( !isset( $info['type'] ) ) {
+ $this->fatalError( "Module '$moduleName' must have a 'type' key." );
+ }
+ switch ( $info['type'] ) {
+ case 'tar':
+ $this->handleTypeTar( $moduleName, $destDir, $info );
+ break;
+ case 'file':
+ $this->handleTypeFile( $moduleName, $destDir, $info );
+ break;
+ case 'multi-file':
+ $this->handleTypeMultiFile( $moduleName, $destDir, $info );
+ break;
+ default:
+ $this->fatalError( "Unknown type '{$info['type']}' for '$moduleName'" );
}
+ }
- // Validate integrity metadata
- $this->output( "... checking integrity of '{$moduleName}'\n" );
- $algo = $integrity === true ? $this->defaultAlgo : explode( '-', $integrity )[0];
- $actualIntegrity = $algo . '-' . base64_encode( hash( $algo, $data, true ) );
- if ( $integrity === true ) {
- $this->output( "Integrity for '{$moduleName}':\n\t${actualIntegrity}\n" );
- continue;
- } elseif ( $integrity !== $actualIntegrity ) {
- $this->fatalError( "Integrity check failed for '{$moduleName}:\n" .
- "Expected: {$integrity}\n" .
- "Actual: {$actualIntegrity}"
+ $this->cleanUp();
+ $this->output( "\nDone!\n" );
+ if ( $this->failAfterOutput ) {
+ // The verify mode should check all modules/files and fail after, not during.
+ return false;
+ }
+ }
+
+ private function fetch( $src, $integrity ) {
+ $data = Http::get( $src, [ 'followRedirects' => false ] );
+ if ( $data === false ) {
+ $this->fatalError( "Failed to download resource at {$src}" );
+ }
+ $algo = $integrity === null ? $this->defaultAlgo : explode( '-', $integrity )[0];
+ $actualIntegrity = $algo . '-' . base64_encode( hash( $algo, $data, true ) );
+ if ( $integrity === $actualIntegrity ) {
+ $this->verbose( "... passed integrity check for {$src}\n" );
+ } else {
+ if ( $this->action === 'make-sri' ) {
+ $this->output( "Integrity for {$src}\n\tintegrity: ${actualIntegrity}\n" );
+ } else {
+ $this->fatalError( "Integrity check failed for {$src}\n" .
+ "\tExpected: {$integrity}\n" .
+ "\tActual: {$actualIntegrity}"
);
}
-
- // Determine destination
- $destDir = "{$IP}/resources/lib/$moduleName";
- $this->output( "... extracting files for '{$moduleName}'\n" );
- $this->handleTypeTar( $moduleName, $data, $destDir, $info );
}
+ return $data;
+ }
- // Clean up
- wfRecursiveRemoveDir( $this->tmpParentDir );
- $this->output( "\nDone!\n" );
+ private function handleTypeFile( $moduleName, $destDir, array $info ) {
+ if ( !isset( $info['src'] ) ) {
+ $this->fatalError( "Module '$moduleName' must have a 'src' key." );
+ }
+ $data = $this->fetch( $info['src'], $info['integrity'] ?? null );
+ $dest = $info['dest'] ?? basename( $info['src'] );
+ $path = "$destDir/$dest";
+ if ( $this->action === 'verify' && sha1_file( $path ) !== sha1( $data ) ) {
+ $this->fatalError( "File for '$moduleName' is different." );
+ } elseif ( $this->action === 'update' ) {
+ wfMkdirParents( $destDir );
+ file_put_contents( "$destDir/$dest", $data );
+ }
}
- private function handleTypeTar( $moduleName, $data, $destDir, array $info ) {
- global $IP;
- wfRecursiveRemoveDir( $this->tmpParentDir );
- if ( !wfMkdirParents( $this->tmpParentDir ) ) {
- $this->fatalError( "Unable to create {$this->tmpParentDir}" );
+ private function handleTypeMultiFile( $moduleName, $destDir, array $info ) {
+ if ( !isset( $info['files'] ) ) {
+ $this->fatalError( "Module '$moduleName' must have a 'files' key." );
}
+ foreach ( $info['files'] as $dest => $file ) {
+ if ( !isset( $file['src'] ) ) {
+ $this->fatalError( "Module '$moduleName' file '$dest' must have a 'src' key." );
+ }
+ $data = $this->fetch( $file['src'], $file['integrity'] ?? null );
+ $path = "$destDir/$dest";
+ if ( $this->action === 'verify' && sha1_file( $path ) !== sha1( $data ) ) {
+ $this->fatalError( "File '$dest' for '$moduleName' is different." );
+ } elseif ( $this->action === 'update' ) {
+ wfMkdirParents( $destDir );
+ file_put_contents( "$destDir/$dest", $data );
+ }
+ }
+ }
- // Write resource to temporary file and open it
+ private function handleTypeTar( $moduleName, $destDir, array $info ) {
+ $info += [ 'src' => null, 'integrity' => null, 'dest' => null ];
+ if ( $info['src'] === null ) {
+ $this->fatalError( "Module '$moduleName' must have a 'src' key." );
+ }
+ // Download the resource to a temporary file and open it
+ $data = $this->fetch( $info['src'], $info['integrity' ] );
$tmpFile = "{$this->tmpParentDir}/$moduleName.tar";
$this->verbose( "... writing '$moduleName' src to $tmpFile\n" );
file_put_contents( $tmpFile, $data );
unset( $data, $p );
if ( $info['dest'] === null ) {
- // Replace the entire directory as-is
- if ( !$this->hasOption( 'update' ) ) {
- $this->output( "[dry run] Would replace /resources/lib/$moduleName\n" );
- } else {
- wfRecursiveRemoveDir( $destDir );
- if ( !rename( $tmpDir, $destDir ) ) {
- $this->fatalError( "Could not move $destDir to $tmpDir." );
- }
- }
- return;
- }
-
- // Create and/or empty the destination
- if ( !$this->hasOption( 'update' ) ) {
- $this->output( "... [dry run] would empty /resources/lib/$moduleName\n" );
+ // Default: Replace the entire directory
+ $toCopy = [ $tmpDir => $destDir ];
} else {
- wfRecursiveRemoveDir( $destDir );
- wfMkdirParents( $destDir );
- }
-
- // Expand and normalise the 'dest' entries
- $toCopy = [];
- foreach ( $info['dest'] as $fromSubPath => $toSubPath ) {
- // Use glob() to expand wildcards and check existence
- $fromPaths = glob( "{$tmpDir}/{$fromSubPath}", GLOB_BRACE );
- if ( !$fromPaths ) {
- $this->fatalError( "Path '$fromSubPath' of '$moduleName' not found." );
- }
- foreach ( $fromPaths as $fromPath ) {
- $toCopy[$fromPath] = $toSubPath === null
- ? "$destDir/" . basename( $fromPath )
- : "$destDir/$toSubPath/" . basename( $fromPath );
+ // Expand and normalise the 'dest' entries
+ $toCopy = [];
+ foreach ( $info['dest'] as $fromSubPath => $toSubPath ) {
+ // Use glob() to expand wildcards and check existence
+ $fromPaths = glob( "{$tmpDir}/{$fromSubPath}", GLOB_BRACE );
+ if ( !$fromPaths ) {
+ $this->fatalError( "Path '$fromSubPath' of '$moduleName' not found." );
+ }
+ foreach ( $fromPaths as $fromPath ) {
+ $toCopy[$fromPath] = $toSubPath === null
+ ? "$destDir/" . basename( $fromPath )
+ : "$destDir/$toSubPath/" . basename( $fromPath );
+ }
}
}
foreach ( $toCopy as $from => $to ) {
- if ( !$this->hasOption( 'update' ) ) {
- $shortFrom = strtr( $from, [ "$tmpDir/" => '' ] );
- $shortTo = strtr( $to, [ "$IP/" => '' ] );
- $this->output( "... [dry run] would move $shortFrom to $shortTo\n" );
- } else {
+ if ( $this->action === 'verify' ) {
+ $this->verbose( "... verifying $to\n" );
+ if ( is_dir( $from ) ) {
+ $rii = new RecursiveIteratorIterator( new RecursiveDirectoryIterator(
+ $from,
+ RecursiveDirectoryIterator::SKIP_DOTS
+ ) );
+ foreach ( $rii as $file ) {
+ $remote = $file->getPathname();
+ $local = strtr( $remote, [ $from => $to ] );
+ if ( sha1_file( $remote ) !== sha1_file( $local ) ) {
+ $this->error( "File '$local' is different." );
+ $this->failAfterOutput = true;
+ }
+ }
+ } elseif ( sha1_file( $from ) !== sha1_file( $to ) ) {
+ $this->error( "File '$to' is different." );
+ $this->failAfterOutput = true;
+ }
+ } elseif ( $this->action === 'update' ) {
$this->verbose( "... moving $from to $to\n" );
wfMkdirParents( dirname( $to ) );
if ( !rename( $from, $to ) ) {
}
}
+ private function cleanUp() {
+ wfRecursiveRemoveDir( $this->tmpParentDir );
+ }
+
+ protected function fatalError( $msg, $exitCode = 1 ) {
+ $this->cleanUp();
+ parent::fatalError( $msg, $exitCode );
+ }
+
/**
* Basic YAML parser.
*
-- The revision ID of the revision that originated the slot's content.
-- To find revisions that changed slots, look for slot_origin = slot_revision_id.
+ -- TODO: Is that actually true? Rollback seems to violate it by setting
+ -- slot_origin to an older rev_id. Undeletions could result in the same situation.
slot_origin bigint unsigned NOT NULL,
PRIMARY KEY ( slot_revision_id, slot_role_id )
+++ /dev/null
-<?php
-require_once __DIR__ . '/Maintenance.php';
-
-/**
- * Fixes all rows affected by https://bugzilla.wikimedia.org/show_bug.cgi?id=37714
- */
-class TidyUpBug37714 extends Maintenance {
- public function execute() {
- // Search for all log entries which are about changing the visability of other log entries.
- $result = $this->getDB( DB_REPLICA )->select(
- 'logging',
- [ 'log_id', 'log_params' ],
- [
- 'log_type' => [ 'suppress', 'delete' ],
- 'log_action' => 'event',
- 'log_namespace' => NS_SPECIAL,
- 'log_title' => SpecialPage::getTitleFor( 'Log' )->getText()
- ],
- __METHOD__
- );
-
- foreach ( $result as $row ) {
- $ids = explode( ',', explode( "\n", $row->log_params )[0] );
- $result = $this->getDB( DB_REPLICA )->select( // Work out what log entries were changed here.
- 'logging',
- 'log_type',
- [ 'log_id' => $ids ],
- __METHOD__,
- 'DISTINCT'
- );
- if ( $result->numRows() === 1 ) {
- // If there's only one type, the target title can be set to include it.
- $logTitle = SpecialPage::getTitleFor( 'Log', $result->current()->log_type )->getText();
- $this->output( 'Set log_title to "' . $logTitle . '" for log entry ' . $row->log_id . ".\n" );
- $this->getDB( DB_MASTER )->update(
- 'logging',
- [ 'log_title' => $logTitle ],
- [ 'log_id' => $row->log_id ],
- __METHOD__
- );
- wfWaitForSlaves();
- }
- }
- }
-}
-
-$maintClass = TidyUpBug37714::class;
-require_once RUN_MAINTENANCE_IF_MAIN;
--- /dev/null
+<?php
+require_once __DIR__ . '/Maintenance.php';
+
+/**
+ * Fixes all rows affected by T39714
+ */
+class TidyUpT39714 extends Maintenance {
+ public function execute() {
+ // Search for all log entries which are about changing the visability of other log entries.
+ $result = $this->getDB( DB_REPLICA )->select(
+ 'logging',
+ [ 'log_id', 'log_params' ],
+ [
+ 'log_type' => [ 'suppress', 'delete' ],
+ 'log_action' => 'event',
+ 'log_namespace' => NS_SPECIAL,
+ 'log_title' => SpecialPage::getTitleFor( 'Log' )->getText()
+ ],
+ __METHOD__
+ );
+
+ foreach ( $result as $row ) {
+ $ids = explode( ',', explode( "\n", $row->log_params )[0] );
+ $result = $this->getDB( DB_REPLICA )->select( // Work out what log entries were changed here.
+ 'logging',
+ 'log_type',
+ [ 'log_id' => $ids ],
+ __METHOD__,
+ 'DISTINCT'
+ );
+ if ( $result->numRows() === 1 ) {
+ // If there's only one type, the target title can be set to include it.
+ $logTitle = SpecialPage::getTitleFor( 'Log', $result->current()->log_type )->getText();
+ $this->output( 'Set log_title to "' . $logTitle . '" for log entry ' . $row->log_id . ".\n" );
+ $this->getDB( DB_MASTER )->update(
+ 'logging',
+ [ 'log_title' => $logTitle ],
+ [ 'log_id' => $row->log_id ],
+ __METHOD__
+ );
+ wfWaitForSlaves();
+ }
+ }
+ }
+}
+
+$maintClass = TidyUpT39714::class;
+require_once RUN_MAINTENANCE_IF_MAIN;
public function fmttime() {
return sprintf( '%5.02f', $this->time );
}
-};
+}
function compare_point( profile_point $a, profile_point $b ) {
// phpcs:ignore MediaWiki.NamingConventions.ValidGlobalName.wgPrefix
],
'targets' => [ 'desktop', 'mobile' ],
],
+ 'mediawiki.widgets.CheckMatrixWidget' => [
+ 'scripts' => [
+ 'resources/src/mediawiki.widgets/mw.widgets.CheckMatrixWidget.js',
+ ],
+ 'dependencies' => [
+ 'oojs-ui-core',
+ ],
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
'mediawiki.widgets.CategoryMultiselectWidget' => [
'scripts' => [
'resources/src/mediawiki.widgets/mw.widgets.CategoryTagItemWidget.js',
mwLoaderTrack = mw.track,
trackCallbacks = $.Callbacks( 'memory' ),
trackHandlers = [],
- hasOwn = Object.prototype.hasOwnProperty,
queue;
/**
* @class mw.hook
*/
mw.hook = ( function () {
- var lists = {};
+ var lists = Object.create( null );
/**
* Create an instance of mw.hook.
* @return {mw.hook}
*/
return function ( name ) {
- var list = hasOwn.call( lists, name ) ?
- lists[ name ] :
- lists[ name ] = $.Callbacks( 'memory' );
+ var list = lists[ name ] || ( lists[ name ] = $.Callbacks( 'memory' ) );
return {
/**
function sortByProperty( array, prop, descending ) {
var order = descending ? -1 : 1;
return array.sort( function ( a, b ) {
+ if ( a[ prop ] === undefined || b[ prop ] === undefined ) {
+ // Sort undefined to the end, regardless of direction
+ return a[ prop ] !== undefined ? -1 : b[ prop ] !== undefined ? 1 : 0;
+ }
return a[ prop ] > b[ prop ] ? order : a[ prop ] < b[ prop ] ? -order : 0;
} );
}
*
* When invoked without arguments, prints all available reports.
*
- * @param {...string} [reports] One or more of "size", "css", or "store".
+ * @param {...string} [reports] One or more of "size", "css", "store", or "time".
*/
inspect.runReports = function () {
var reports = arguments.length > 0 ?
};
/**
+ * @private
* @class mw.inspect.reports
* @singleton
*/
} catch ( e ) {}
}
return [ stats ];
+ },
+
+ /**
+ * Generate a breakdown of all loaded modules and their time
+ * spent during initialisation (measured in milliseconds).
+ *
+ * This timing data is collected by mw.loader.profiler.
+ *
+ * @return {Object[]} Table rows
+ */
+ time: function () {
+ var modules;
+
+ if ( !mw.loader.profiler ) {
+ mw.log.warn( 'mw.inspect: The time report requires $wgResourceLoaderEnableJSProfiler.' );
+ return [];
+ }
+
+ modules = inspect.getLoadedModules()
+ .map( function ( moduleName ) {
+ return mw.loader.profiler.getProfile( moduleName );
+ } )
+ .filter( function ( perf ) {
+ // Exclude modules that reached "ready" state without involvement from mw.loader.
+ // This is primarily styles-only as loaded via <link rel="stylesheet">.
+ return perf !== null;
+ } );
+
+ // Sort by total time spent, highest first.
+ sortByProperty( modules, 'total', true );
+
+ // Add human-readable strings
+ modules.forEach( function ( module ) {
+ module.totalInMs = module.total;
+ module.total = module.totalInMs.toLocaleString() + ' ms';
+ } );
+
+ return modules;
}
};
'use strict';
var notification,
- // The #mw-notification-area div that all notifications are contained inside.
+ // The .mw-notification-area div that all notifications are contained inside.
$area,
// Number of open notification boxes at any time
openNotificationCount = 0,
// Write to the DOM:
// Prepend the notification area to the content area and save its object.
+ // The ID attribute here is deprecated.
$area = $( '<div id="mw-notification-area" class="mw-notification-area mw-notification-area-layout"></div>' )
// Pause auto-hide timers when the mouse is in the notification area.
.on( {
* @class mw.user
* @singleton
*/
-/* global Uint32Array */
+/* global Uint16Array */
( function ( mw, $ ) {
var userInfoPromise, pageviewRandomId;
* mobile usages of this code is probably higher.
*
* Rationale:
- * We need about 64 bits to make sure that probability of collision
- * on 500 million (5*10^8) is <= 1%
- * See https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
+ * We need about 80 bits to make sure that probability of collision
+ * on 155 billion is <= 1%
*
- * @return {string} 64 bit integer in hex format, padded
+ * See https://en.wikipedia.org/wiki/Birthday_attack#Mathematics
+ * n(p;H) = n(0.01,2^80)= sqrt (2 * 2^80 * ln(1/(1-0.01)))
+
+ * @return {string} 80 bit integer in hex format, padded
*/
generateRandomSessionId: function () {
var rnds, i,
- hexRnds = new Array( 2 ),
// Support: IE 11
crypto = window.crypto || window.msCrypto;
- if ( crypto && crypto.getRandomValues && typeof Uint32Array === 'function' ) {
- // Fill an array with 2 random values, each of which is 32 bits.
- // Note that Uint32Array is array-like but does not implement Array.
- rnds = new Uint32Array( 2 );
+ if ( crypto && crypto.getRandomValues && typeof Uint16Array === 'function' ) {
+ // Fill an array with 5 random values, each of which is 16 bits.
+ // Note that Uint16Array is array-like but does not implement Array.
+ rnds = new Uint16Array( 5 );
crypto.getRandomValues( rnds );
} else {
- rnds = [
- Math.floor( Math.random() * 0x100000000 ),
- Math.floor( Math.random() * 0x100000000 )
- ];
- }
- // Convert number to a string with 16 hex characters
- for ( i = 0; i < 2; i++ ) {
- // Add 0x100000000 before converting to hex and strip the extra character
- // after converting to keep the leading zeros.
- hexRnds[ i ] = ( rnds[ i ] + 0x100000000 ).toString( 16 ).slice( 1 );
+ // 0x10000 is 2^16 so the operation below will return a number
+ // between 2^16 and zero
+ for ( i = 0; i < 5; i++ ) {
+ rnds[ i ] = Math.floor( Math.random() * 0x10000 );
+ }
}
+ // Convert the 5 16bit-numbers into 20 characters (4 hex per 16 bits).
// Concatenation of two random integers with entropy n and m
- // returns a string with entropy n+m if those strings are independent
- return hexRnds.join( '' );
+ // returns a string with entropy n+m if those strings are independent.
+ // Tested that below code is faster than array + loop + join.
+ return ( rnds[ 0 ] + 0x10000 ).toString( 16 ).slice( 1 ) +
+ ( rnds[ 1 ] + 0x10000 ).toString( 16 ).slice( 1 ) +
+ ( rnds[ 2 ] + 0x10000 ).toString( 16 ).slice( 1 ) +
+ ( rnds[ 3 ] + 0x10000 ).toString( 16 ).slice( 1 ) +
+ ( rnds[ 4 ] + 0x10000 ).toString( 16 ).slice( 1 );
},
/**
--- /dev/null
+( function ( $, mw ) {
+ /**
+ * A JavaScript version of CheckMatrixWidget.
+ *
+ * @class
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {Object} columns Required object representing the column labels and associated
+ * tags in the matrix.
+ * @cfg {Object} rows Required object representing the row labels and associated
+ * tags in the matrix.
+ * @cfg {string[]} [forcedOn] An array of column-row tags to be displayed as
+ * enabled but unavailable to change
+ * @cfg {string[]} [forcedOff] An array of column-row tags to be displayed as
+ * disnabled but unavailable to change
+ * @cfg {Object} Object mapping row label to tooltip content
+ */
+ mw.widgets.CheckMatrixWidget = function MWWCheckMatrixWidget( config ) {
+ var $headRow = $( '<tr>' ),
+ $table = $( '<table>' ),
+ widget = this;
+ config = config || {};
+
+ // Parent constructor
+ mw.widgets.CheckMatrixWidget.parent.call( this, config );
+ this.checkboxes = {};
+ this.name = config.name;
+ this.id = config.id;
+ this.rows = config.rows || {};
+ this.columns = config.columns || {};
+ this.tooltips = config.tooltips || [];
+ this.values = config.values || [];
+ this.forcedOn = config.forcedOn || [];
+ this.forcedOff = config.forcedOff || [];
+
+ // Build header
+ $headRow.append( $( '<td>' ).html( ' ' ) );
+
+ // Iterate over the columns object (ignore the value)
+ $.each( this.columns, function ( columnLabel ) {
+ $headRow.append( $( '<td>' ).text( columnLabel ) );
+ } );
+ $table.append( $headRow );
+
+ // Build table
+ $.each( this.rows, function ( rowLabel, rowTag ) {
+ var $row = $( '<tr>' ),
+ labelField = new OO.ui.FieldLayout(
+ new OO.ui.Widget(), // Empty widget, since we don't have the checkboxes here
+ {
+ label: rowLabel,
+ help: widget.tooltips[ rowLabel ],
+ align: 'inline'
+ }
+ );
+
+ // Label
+ $row.append( $( '<td>' ).append( labelField.$element ) );
+
+ // Columns
+ $.each( widget.columns, function ( columnLabel, columnTag ) {
+ var thisTag = columnTag + '-' + rowTag,
+ checkbox = new OO.ui.CheckboxInputWidget( {
+ value: thisTag,
+ name: widget.name ? widget.name + '[]' : undefined,
+ id: widget.id ? widget.id + '-' + thisTag : undefined,
+ selected: widget.isTagSelected( thisTag ),
+ disabled: widget.isTagDisabled( thisTag )
+ } );
+
+ widget.checkboxes[ thisTag ] = checkbox;
+ $row.append( $( '<td>' ).append( checkbox.$element ) );
+ } );
+
+ $table.append( $row );
+ } );
+
+ this.$element
+ .addClass( 'mw-widget-checkMatrixWidget' )
+ .append( $table );
+ };
+
+ /* Setup */
+
+ OO.inheritClass( mw.widgets.CheckMatrixWidget, OO.ui.Widget );
+
+ /* Methods */
+
+ /**
+ * Check whether the given tag is selected
+ *
+ * @param {string} tagName Tag name
+ * @return {boolean} Tag is selected
+ */
+ mw.widgets.CheckMatrixWidget.prototype.isTagSelected = function ( tagName ) {
+ return (
+ // If tag is not forced off
+ this.forcedOff.indexOf( tagName ) === -1 &&
+ (
+ // If tag is in values
+ this.values.indexOf( tagName ) > -1 ||
+ // If tag is forced on
+ this.forcedOn.indexOf( tagName ) > -1
+ )
+ );
+ };
+
+ /**
+ * Check whether the given tag is disabled
+ *
+ * @param {string} tagName Tag name
+ * @return {boolean} Tag is disabled
+ */
+ mw.widgets.CheckMatrixWidget.prototype.isTagDisabled = function ( tagName ) {
+ return (
+ // If the entire widget is disabled
+ this.isDisabled() ||
+ // If tag is forced off or forced on
+ this.forcedOff.indexOf( tagName ) > -1 ||
+ this.forcedOn.indexOf( tagName ) > -1
+ );
+ };
+ /**
+ * @inheritdoc
+ */
+ mw.widgets.CheckMatrixWidget.prototype.setDisabled = function ( isDisabled ) {
+ var widget = this;
+
+ // Parent method
+ mw.widgets.CheckMatrixWidget.parent.prototype.setDisabled.call( this, isDisabled );
+
+ // setDisabled sometimes gets called before the widget is ready
+ if ( this.checkboxes && Object.keys( this.checkboxes ).length > 0 ) {
+ // Propagate to all checkboxes and update their disabled state
+ $.each( this.checkboxes, function ( name, checkbox ) {
+ checkbox.setDisabled( widget.isTagDisabled( name ) );
+ } );
+ }
+ };
+}( jQuery, mediaWiki ) );
* @alternateClassName mediaWiki
* @singleton
*/
-/* global $VARS */
+/* global $VARS, $CODE */
( function () {
'use strict';
* @class
*/
function StringSet() {
- this.set = {};
+ this.set = Object.create( null );
}
StringSet.prototype.add = function ( value ) {
this.set[ value ] = true;
};
StringSet.prototype.has = function ( value ) {
- return hasOwn.call( this.set, value );
+ return value in this.set;
};
return StringSet;
}() );
* copied in one direction only. Changes to globals do not reflect in the map.
*/
function Map( global ) {
- this.values = {};
+ this.values = Object.create( null );
if ( global === true ) {
// Override #set to also set the global variable
this.set = function ( selection, value ) {
results = {};
for ( i = 0; i < selection.length; i++ ) {
if ( typeof selection[ i ] === 'string' ) {
- results[ selection[ i ] ] = hasOwn.call( this.values, selection[ i ] ) ?
+ results[ selection[ i ] ] = selection[ i ] in this.values ?
this.values[ selection[ i ] ] :
fallback;
}
}
if ( typeof selection === 'string' ) {
- return hasOwn.call( this.values, selection ) ?
+ return selection in this.values ?
this.values[ selection ] :
fallback;
}
var i;
if ( Array.isArray( selection ) ) {
for ( i = 0; i < selection.length; i++ ) {
- if ( typeof selection[ i ] !== 'string' || !hasOwn.call( this.values, selection[ i ] ) ) {
+ if ( typeof selection[ i ] !== 'string' || !( selection[ i ] in this.values ) ) {
return false;
}
}
return true;
}
- return typeof selection === 'string' && hasOwn.call( this.values, selection );
+ return typeof selection === 'string' && selection in this.values;
}
};
*/
marker = document.querySelector( 'meta[name="ResourceLoaderDynamicStyles"]' ),
- // For addEmbeddedCSS()
- cssBuffer = '',
- cssBufferTimer = null,
- cssCallbacks = [],
+ // For #addEmbeddedCSS()
+ nextCssBuffer,
rAF = window.requestAnimationFrame || setTimeout;
/**
return el;
}
+ /**
+ * @private
+ * @param {Object} cssBuffer
+ */
+ function flushCssBuffer( cssBuffer ) {
+ var i;
+ // Mark this object as inactive now so that further calls to addEmbeddedCSS() from
+ // the callbacks go to a new buffer instead of this one (T105973)
+ cssBuffer.active = false;
+ newStyleTag( cssBuffer.cssText, marker );
+ for ( i = 0; i < cssBuffer.callbacks.length; i++ ) {
+ cssBuffer.callbacks[ i ]();
+ }
+ }
+
/**
* Add a bit of CSS text to the current browser page.
*
- * The CSS will be appended to an existing ResourceLoader-created `<style>` tag
- * or create a new one based on whether the given `cssText` is safe for extension.
+ * The creation and insertion of the `<style>` element is debounced for two reasons:
+ *
+ * - Performing the insertion before the next paint round via requestAnimationFrame
+ * avoids forced or wasted style recomputations, which are expensive in browsers.
+ * - Reduce how often new stylesheets are inserted by letting additional calls to this
+ * function accumulate into a buffer for at least one JavaScript tick. Modules are
+ * received from the server in batches, which means there is likely going to be many
+ * calls to this function in a row within the same tick / the same call stack.
+ * See also T47810.
*
* @private
- * @param {string} [cssText=cssBuffer] If called without cssText,
- * the internal buffer will be inserted instead.
- * @param {Function} [callback]
+ * @param {string} cssText CSS text to be added in a `<style>` tag.
+ * @param {Function} callback Called after the insertion has occurred
*/
function addEmbeddedCSS( cssText, callback ) {
- function fireCallbacks() {
- var i,
- oldCallbacks = cssCallbacks;
- // Reset cssCallbacks variable so it's not polluted by any calls to
- // addEmbeddedCSS() from one of the callbacks (T105973)
- cssCallbacks = [];
- for ( i = 0; i < oldCallbacks.length; i++ ) {
- oldCallbacks[ i ]();
- }
- }
-
- if ( callback ) {
- cssCallbacks.push( callback );
+ // Create a buffer if:
+ // - We don't have one yet.
+ // - The previous one is closed.
+ // - The next CSS chunk syntactically needs to be at the start of a stylesheet (T37562).
+ if ( !nextCssBuffer || nextCssBuffer.active === false || cssText.slice( 0, '@import'.length ) === '@import' ) {
+ nextCssBuffer = {
+ cssText: '',
+ callbacks: [],
+ active: null
+ };
}
- // Yield once before creating the <style> tag. This lets multiple stylesheets
- // accumulate into one buffer, allowing us to reduce how often new stylesheets
- // are inserted in the browser. Appending a stylesheet and waiting for the
- // browser to repaint is fairly expensive. (T47810)
- if ( cssText ) {
- // Don't extend the buffer if the item needs its own stylesheet.
- // Keywords like `@import` are only valid at the start of a stylesheet (T37562).
- if ( !cssBuffer || cssText.slice( 0, '@import'.length ) !== '@import' ) {
- // Linebreak for somewhat distinguishable sections
- cssBuffer += '\n' + cssText;
- if ( !cssBufferTimer ) {
- cssBufferTimer = rAF( function () {
- // Wrap in anonymous function that takes no arguments
- // Support: Firefox < 13
- // Firefox 12 has non-standard behaviour of passing a number
- // as first argument to a setTimeout callback.
- // http://benalman.com/news/2009/07/the-mysterious-firefox-settime/
- addEmbeddedCSS();
- } );
- }
- return;
- }
+ // Linebreak for somewhat distinguishable sections
+ nextCssBuffer.cssText += '\n' + cssText;
+ nextCssBuffer.callbacks.push( callback );
- // This is a scheduled flush for the buffer
- } else {
- cssBufferTimer = null;
- cssText = cssBuffer;
- cssBuffer = '';
+ if ( nextCssBuffer.active === null ) {
+ nextCssBuffer.active = true;
+ // The flushCssBuffer callback has its parameter bound by reference, which means
+ // 1) We can still extend the buffer from our object reference after this point.
+ // 2) We can safely re-assign the variable (not the object) to start a new buffer.
+ rAF( flushCssBuffer.bind( null, nextCssBuffer ) );
}
-
- newStyleTag( cssText, marker );
-
- fireCallbacks();
}
/**
* See also #work().
*
* @private
- * @param {string|string[]} dependencies Module name or array of string module names
+ * @param {string[]} dependencies Array of module names in the registry
* @param {Function} [ready] Callback to execute when all dependencies are ready
* @param {Function} [error] Callback to execute when any dependency fails
*/
function enqueue( dependencies, ready, error ) {
- // Allow calling by single module name
- if ( typeof dependencies === 'string' ) {
- dependencies = [ dependencies ];
- }
-
if ( allReady( dependencies ) ) {
// Run ready immediately
if ( ready !== undefined ) {
ready();
}
-
return;
}
dependencies
);
}
-
return;
}
jobs.push( {
// Narrow down the list to modules that are worth waiting for
dependencies: dependencies.filter( function ( module ) {
- var state = mw.loader.getState( module );
+ var state = registry[ module ].state;
return state === 'registered' || state === 'loaded' || state === 'loading' || state === 'executing';
} ),
ready: ready,
}
dependencies.forEach( function ( module ) {
- var state = mw.loader.getState( module );
// Only queue modules that are still in the initial 'registered' state
// (not ones already loading, ready or error).
- if ( state === 'registered' && queue.indexOf( module ) === -1 ) {
+ if ( registry[ module ].state === 'registered' && queue.indexOf( module ) === -1 ) {
// Private modules must be embedded in the page. Don't bother queuing
// these as the server will deny them anyway (T101806).
if ( registry[ module ].group === 'private' ) {
* @param {string} module Module name to execute
*/
function execute( module ) {
- var key, value, media, i, urls, cssHandle, checkCssHandles, runScript,
- cssHandlesRegistered = false;
+ var key, value, media, i, urls, cssHandle, siteDeps, siteDepErr, runScript,
+ cssPending = 0;
if ( !hasOwn.call( registry, module ) ) {
throw new Error( 'Module has not been registered yet: ' + module );
}
registry[ module ].state = 'executing';
+ $CODE.profileExecuteStart();
runScript = function () {
var script, markModuleReady, nestedAddScript;
+ $CODE.profileScriptStart();
script = registry[ module ].script;
markModuleReady = function () {
+ $CODE.profileScriptEnd();
registry[ module ].state = 'ready';
handlePending( module );
};
// Use mw.track instead of mw.log because these errors are common in production mode
// (e.g. undefined variable), and mw.log is only enabled in debug mode.
registry[ module ].state = 'error';
+ $CODE.profileScriptEnd();
mw.trackError( 'resourceloader.exception', {
exception: e, module:
module, source: 'module-execute'
mw.templates.set( module, registry[ module ].templates );
}
- // Make sure we don't run the scripts until all stylesheet insertions have completed.
- ( function () {
- var pending = 0;
- checkCssHandles = function () {
- var ex, dependencies;
- // cssHandlesRegistered ensures we don't take off too soon, e.g. when
- // one of the cssHandles is fired while we're still creating more handles.
- if ( cssHandlesRegistered && pending === 0 && runScript ) {
- if ( module === 'user' ) {
- // Implicit dependency on the site module. Not real dependency because
- // it should run after 'site' regardless of whether it succeeds or fails.
- // Note: This is a simplified version of mw.loader.using(), inlined here
- // as using() depends on jQuery (T192623).
- try {
- dependencies = resolve( [ 'site' ] );
- } catch ( e ) {
- ex = e;
- runScript();
- }
- if ( ex === undefined ) {
- enqueue( dependencies, runScript, runScript );
- }
- } else {
- runScript();
- }
- runScript = undefined; // Revoke
+ // Adding of stylesheets is asynchronous via addEmbeddedCSS().
+ // The below function uses a counting semaphore to make sure we don't call
+ // runScript() until after this module's stylesheets have been inserted
+ // into the DOM.
+ cssHandle = function () {
+ // Increase semaphore, when creating a callback for addEmbeddedCSS.
+ cssPending++;
+ return function () {
+ var runScriptCopy;
+ // Decrease semaphore, when said callback is invoked.
+ cssPending--;
+ if ( cssPending === 0 ) {
+ // Paranoia:
+ // This callback is exposed to addEmbeddedCSS, which is outside the execute()
+ // function and is not concerned with state-machine integrity. In turn,
+ // addEmbeddedCSS() actually exposes stuff further into the browser (rAF).
+ // If increment and decrement callbacks happen in the wrong order, or start
+ // again afterwards, then this branch could be reached multiple times.
+ // To protect the integrity of the state-machine, prevent that from happening
+ // by making runScript() cannot be called more than once. We store a private
+ // reference when we first reach this branch, then deference the original, and
+ // call our reference to it.
+ runScriptCopy = runScript;
+ runScript = undefined;
+ runScriptCopy();
}
};
- cssHandle = function () {
- var check = checkCssHandles;
- pending++;
- return function () {
- if ( check ) {
- pending--;
- check();
- check = undefined; // Revoke
- }
- };
- };
- }() );
+ };
// Process styles (see also mw.loader.implement)
// * back-compat: { <media>: css }
}
}
- // Kick off.
- cssHandlesRegistered = true;
- checkCssHandles();
+ // End profiling of execute()-self before we call runScript(),
+ // which we want to measure separately without overlap.
+ $CODE.profileExecuteEnd();
+
+ if ( module === 'user' ) {
+ // Implicit dependency on the site module. Not a real dependency because it should
+ // run after 'site' regardless of whether it succeeds or fails.
+ // Note: This is a simplified version of mw.loader.using(), inlined here because
+ // mw.loader.using() is part of mediawiki.base (depends on jQuery; T192623).
+ try {
+ siteDeps = resolve( [ 'site' ] );
+ } catch ( e ) {
+ siteDepErr = e;
+ runScript();
+ }
+ if ( siteDepErr === undefined ) {
+ enqueue( siteDeps, runScript, runScript );
+ }
+ } else if ( cssPending === 0 ) {
+ // Regular module without styles
+ runScript();
+ }
+ // else: runScript will get called via cssHandle()
}
function sortQuery( o ) {
* @param {string[]} batch
*/
function batchRequest( batch ) {
- var reqBase, splits, maxQueryLength, b, bSource, bGroup, bSourceGroup,
+ var reqBase, splits, maxQueryLength, b, bSource, bGroup,
source, group, i, modules, sourceLoadScript,
currReqBase, currReqBaseLength, moduleMap, currReqModules, l,
lastDotIndex, prefix, suffix, bytesAdded;
maxQueryLength = mw.config.get( 'wgResourceLoaderMaxQueryLength', 2000 );
// Split module list by source and by group.
- splits = {};
+ splits = Object.create( null );
for ( b = 0; b < batch.length; b++ ) {
bSource = registry[ batch[ b ] ].source;
bGroup = registry[ batch[ b ] ].group;
- if ( !hasOwn.call( splits, bSource ) ) {
- splits[ bSource ] = {};
+ if ( !splits[ bSource ] ) {
+ splits[ bSource ] = Object.create( null );
}
- if ( !hasOwn.call( splits[ bSource ], bGroup ) ) {
+ if ( !splits[ bSource ][ bGroup ] ) {
splits[ bSource ][ bGroup ] = [];
}
- bSourceGroup = splits[ bSource ][ bGroup ];
- bSourceGroup.push( batch[ b ] );
+ splits[ bSource ][ bGroup ].push( batch[ b ] );
}
for ( source in splits ) {
-
sourceLoadScript = sources[ source ];
for ( group in splits[ source ] ) {
// We may need to split up the request to honor the query string length limit,
// so build it piece by piece.
l = currReqBaseLength;
- moduleMap = {}; // { prefix: [ suffixes ] }
+ moduleMap = Object.create( null ); // { prefix: [ suffixes ] }
currReqModules = [];
for ( i = 0; i < modules.length; i++ ) {
// If lastDotIndex is -1, substr() returns an empty string
prefix = modules[ i ].substr( 0, lastDotIndex );
suffix = modules[ i ].slice( lastDotIndex + 1 );
- bytesAdded = hasOwn.call( moduleMap, prefix ) ?
+ bytesAdded = moduleMap[ prefix ] ?
suffix.length + 3 : // '%2C'.length == 3
modules[ i ].length + 3; // '%7C'.length == 3
doRequest();
// .. and start again.
l = currReqBaseLength;
- moduleMap = {};
+ moduleMap = Object.create( null );
currReqModules = [];
mw.track( 'resourceloader.splitRequest', { maxQueryLength: maxQueryLength } );
}
- if ( !hasOwn.call( moduleMap, prefix ) ) {
+ if ( !moduleMap[ prefix ] ) {
moduleMap[ prefix ] = [];
}
l += bytesAdded;
* a list of arguments compatible with this method
* @param {string|number} version Module version hash (falls backs to empty string)
* Can also be a number (timestamp) for compatibility with MediaWiki 1.25 and earlier.
- * @param {string|Array} dependencies One string or array of strings of module
- * names on which this module depends.
+ * @param {string[]} [dependencies] Array of module names on which this module depends.
* @param {string} [group=null] Group which the module is in
* @param {string} [source='local'] Name of the source
* @param {string} [skip=null] Script body of the skip function
*/
register: function ( module, version, dependencies, group, source, skip ) {
- var i, deps;
+ var i;
// Allow multiple registration
if ( typeof module === 'object' ) {
resolveIndexedDependencies( module );
if ( hasOwn.call( registry, module ) ) {
throw new Error( 'module already registered: ' + module );
}
- if ( typeof dependencies === 'string' ) {
- // A single module name
- deps = [ dependencies ];
- } else if ( typeof dependencies === 'object' ) {
- // Array of module names
- deps = dependencies;
- }
// List the module as registered
registry[ module ] = {
// Exposed to execute() for mw.loader.implement() closures.
module: {
exports: {}
},
- version: version !== undefined ? String( version ) : '',
- dependencies: deps || [],
+ version: String( version || '' ),
+ dependencies: dependencies || [],
group: typeof group === 'string' ? group : null,
source: typeof source === 'string' ? source : 'local',
state: 'registered',
/**
* Change the state of one or more modules.
*
- * @param {Object|string} modules Object of module name/state pairs
+ * @param {Object} modules Object of module name/state pairs
*/
state: function ( modules ) {
var module, state;
* modules and cache each of them separately, using each module's versioning scheme
* to determine when the cache should be invalidated.
*
+ * @private
* @singleton
* @class mw.loader.store
*/
* @return {string} String of concatenated vary conditions.
*/
getVary: function () {
- return [
- mw.config.get( 'skin' ),
- mw.config.get( 'wgResourceLoaderStorageVersion' ),
- mw.config.get( 'wgUserLanguage' )
- ].join( ':' );
+ return mw.config.get( 'skin' ) + ':' +
+ mw.config.get( 'wgResourceLoaderStorageVersion' ) + ':' +
+ mw.config.get( 'wgUserLanguage' );
},
/**
}
try {
+ // This a string we stored, or `null` if the key does not (yet) exist.
raw = localStorage.getItem( mw.loader.store.getStoreKey() );
// If we get here, localStorage is available; mark enabled
mw.loader.store.enabled = true;
+ // If null, JSON.parse() will cast to string and re-parse, still null.
data = JSON.parse( raw );
if ( data && typeof data.items === 'object' && data.vary === mw.loader.store.getVary() ) {
mw.loader.store.items = data.items;
} );
}
+ // If we get here, one of four things happened:
+ //
+ // 1. localStorage did not contain our store key.
+ // This means `raw` is `null`, and we're on a fresh page view (cold cache).
+ // The store was enabled, and `items` starts fresh.
+ //
+ // 2. localStorage contained parseable data under our store key,
+ // but it's not applicable to our current context (see getVary).
+ // The store was enabled, and `items` starts fresh.
+ //
+ // 3. JSON.parse threw (localStorage contained corrupt data).
+ // This means `raw` contains a string.
+ // The store was enabled, and `items` starts fresh.
+ //
+ // 4. localStorage threw (disabled or otherwise unavailable).
+ // This means `raw` was never assigned.
+ // We will disable the store below.
if ( raw === undefined ) {
// localStorage failed; disable store
mw.loader.store.enabled = false;
- } else {
- mw.loader.store.update();
}
},
*
* @param {string} module Module name
* @param {Object} descriptor The module's descriptor as set in the registry
- * @return {boolean} Module was set
*/
set: function ( module, descriptor ) {
var args, key, src;
if ( !mw.loader.store.enabled ) {
- return false;
+ return;
}
key = getModuleKey( module );
descriptor.templates ].indexOf( undefined ) !== -1
) {
// Decline to store
- return false;
+ return;
}
try {
JSON.stringify( descriptor.messages ),
JSON.stringify( descriptor.templates )
];
- // Attempted workaround for a possible Opera bug (bug T59567).
- // This regex should never match under sane conditions.
- if ( /^\s*\(/.test( args[ 1 ] ) ) {
- args[ 1 ] = 'function' + args[ 1 ];
- mw.trackError( 'resourceloader.assert', { source: 'bug-T59567' } );
- }
} catch ( e ) {
mw.trackError( 'resourceloader.exception', {
exception: e,
source: 'store-localstorage-json'
} );
- return false;
+ return;
}
src = 'mw.loader.implement(' + args.join( ',' ) + ');';
if ( src.length > mw.loader.store.MODULE_SIZE_MAX ) {
- return false;
+ return;
}
mw.loader.store.items[ key ] = src;
mw.loader.store.update();
- return true;
},
/**
* Iterate through the module store, removing any item that does not correspond
* (in name and version) to an item in the module registry.
- *
- * @return {boolean} Store was pruned
*/
prune: function () {
var key, module;
- if ( !mw.loader.store.enabled ) {
- return false;
- }
-
for ( key in mw.loader.store.items ) {
module = key.slice( 0, key.indexOf( '@' ) );
if ( getModuleKey( module ) !== key ) {
delete mw.loader.store.items[ key ];
}
}
- return true;
},
/**
mw.loader.store.items = {};
try {
localStorage.removeItem( mw.loader.store.getStoreKey() );
- } catch ( ignored ) {}
+ } catch ( e ) {}
},
/**
* Sync in-memory store back to localStorage.
*
- * This function debounces updates. When called with a flush already pending,
- * the call is coalesced into the pending update. The call to
- * localStorage.setItem will be naturally deferred until the page is quiescent.
+ * This function debounces updates. When called with a flush already pending, the
+ * scheduled flush is postponed. The call to localStorage.setItem will be keep
+ * being deferred until the page is quiescent for 2 seconds.
*
* Because localStorage is shared by all pages from the same origin, if multiple
* pages are loaded with different module sets, the possibility exists that
- * modules saved by one page will be clobbered by another. But the impact would
- * be minor and the problem would be corrected by subsequent page views.
+ * modules saved by one page will be clobbered by another. The only impact of this
+ * is minor (merely a less efficient cache use) and the problem would be corrected
+ * by subsequent page views.
*
* @method
*/
update: ( function () {
- var hasPendingWrite = false;
+ var timer, hasPendingWrites = false;
function flushWrites() {
var data, key;
- if ( !hasPendingWrite || !mw.loader.store.enabled ) {
+ if ( !mw.loader.store.enabled ) {
return;
}
} );
}
- hasPendingWrite = false;
+ hasPendingWrites = false;
}
- return function () {
- if ( !hasPendingWrite ) {
- hasPendingWrite = true;
+ function request() {
+ // If another mw.loader.store.set()/update() call happens in the narrow
+ // time window between requestIdleCallback() and flushWrites firing, ignore it.
+ // It'll be saved by the already-scheduled flush.
+ if ( !hasPendingWrites ) {
+ hasPendingWrites = true;
mw.requestIdleCallback( flushWrites );
}
+ }
+
+ return function () {
+ // Cancel the previous timer (if it hasn't fired yet)
+ clearTimeout( timer );
+ timer = setTimeout( request, 2000 );
};
}() )
}
--- /dev/null
+/*!
+ * Augment mw.loader to facilitate module-level profiling.
+ *
+ * @author Timo Tijhof
+ * @since 1.32
+ */
+/* global mw */
+( function () {
+ 'use strict';
+
+ var moduleTimes = Object.create( null );
+
+ /**
+ * Private hooks inserted into mw.loader code if MediaWiki configuration
+ * `$wgResourceLoaderEnableJSProfiler` is `true`.
+ *
+ * To use this data, run `mw.inspect( 'time' )` from the browser console.
+ * See mw#inspect().
+ *
+ * @private
+ * @class
+ * @singleton
+ */
+ mw.loader.profiler = {
+ onExecuteStart: function ( moduleName ) {
+ var time = performance.now();
+ if ( moduleTimes[ moduleName ] ) {
+ throw new Error( 'Unexpected perf record for "' + moduleName + '".' );
+ }
+ moduleTimes[ moduleName ] = {
+ executeStart: time,
+ executeEnd: null,
+ scriptStart: null,
+ scriptEnd: null
+ };
+ },
+ onExecuteEnd: function ( moduleName ) {
+ var time = performance.now();
+ moduleTimes[ moduleName ].executeEnd = time;
+ },
+ onScriptStart: function ( moduleName ) {
+ var time = performance.now();
+ moduleTimes[ moduleName ].scriptStart = time;
+ },
+ onScriptEnd: function ( moduleName ) {
+ var time = performance.now();
+ moduleTimes[ moduleName ].scriptEnd = time;
+ },
+
+ /**
+ * For internal use by inspect.reports#time.
+ *
+ * @private
+ * @param {string} moduleName
+ * @return {Object|null}
+ * @throws {Error} If the perf record is incomplete.
+ */
+ getProfile: function ( moduleName ) {
+ var times, key, execute, script, total;
+ times = moduleTimes[ moduleName ];
+ if ( !times ) {
+ return null;
+ }
+ for ( key in times ) {
+ if ( times[ key ] === null ) {
+ throw new Error( 'Incomplete perf record for "' + moduleName + '".', times );
+ }
+ }
+ execute = times.executeEnd - times.executeStart;
+ script = times.scriptEnd - times.scriptStart;
+ total = execute + script;
+ return {
+ name: moduleName,
+ execute: execute,
+ script: script,
+ total: total
+ };
+ }
+ };
+
+}() );
// This embeds mediawiki.js, which defines 'mw' and 'mw.loader'.
$CODE.defineLoader();
- mw.requestIdleCallback( startUp );
+ startUp();
}() );
"PhanUndeclaredMethod",
// approximate error count: 1224
"PhanUndeclaredProperty",
- // approximate error count: 3
- "PhanUndeclaredStaticMethod",
// approximate error count: 58
"PhanUndeclaredVariableDim",
],
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\IMaintainableDatabase;
use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\LBFactory;
use Wikimedia\TestingAccessWrapper;
*/
private $loggers = [];
+ /**
+ * @var LoggerInterface
+ */
+ private $testLogger;
+
/**
* Table name prefixes. Oracle likes it shorter.
*/
$this->backupGlobals = false;
$this->backupStaticAttributes = false;
+ $this->testLogger = self::getTestLogger();
+ }
+
+ private static function getTestLogger() {
+ return LoggerFactory::getInstance( 'tests-phpunit' );
}
public function __destruct() {
if ( !self::$dbSetup || $this->needsDB() ) {
// set up a DB connection for this test to use
+ $this->testLogger->info( "Setting up DB for " . $this->toString() );
self::$useTemporaryTables = !$this->getCliArg( 'use-normal-tables' );
self::$reuseDB = $this->getCliArg( 'reuse-db' );
$needsResetDB = true;
}
+ $this->testLogger->info( "Starting test " . $this->toString() );
parent::run( $result );
+ $this->testLogger->info( "Finished test " . $this->toString() );
if ( $needsResetDB ) {
+ $this->testLogger->info( "Resetting DB" );
$this->resetDB( $this->db, $this->tablesUsed );
}
}
}
// Check for unsafe queries
if ( $this->db->getType() === 'mysql' ) {
- $this->db->query( "SET sql_mode = 'STRICT_ALL_TABLES'" );
+ $this->db->query( "SET sql_mode = 'STRICT_ALL_TABLES'", __METHOD__ );
}
}
$this->db->rollback( __METHOD__, 'flush' );
}
if ( $this->db->getType() === 'mysql' ) {
- $this->db->query( "SET sql_mode = " . $this->db->addQuotes( $wgSQLMode ) );
+ $this->db->query( "SET sql_mode = " . $this->db->addQuotes( $wgSQLMode ),
+ __METHOD__ );
}
}
* @since 1.32
*/
protected function addCoreDBData() {
+ $this->testLogger->info( __METHOD__ );
if ( $this->db->getType() == 'oracle' ) {
# Insert 0 user to prevent FK violations
# Anonymous user
// Assuming this isn't needed for External Store database, and not sure if the procedure
// would be available there.
if ( $db->getType() == 'oracle' ) {
- $db->query( 'BEGIN FILL_WIKI_INFO; END;' );
+ $db->query( 'BEGIN FILL_WIKI_INFO; END;', __METHOD__ );
}
Hooks::run( 'UnitTestsAfterDatabaseSetup', [ $db, $prefix ] );
*/
private function resetDB( $db, $tablesUsed ) {
if ( $db ) {
+ // NOTE: Do not reset the slot_roles and content_models tables, but let them
+ // leak across tests. Resetting them would require to reset all NamedTableStore
+ // instances for these tables, of which there may be several beyond the ones
+ // known to MediaWikiServices. See T202641.
$userTables = [ 'user', 'user_groups', 'user_properties', 'actor' ];
$pageTables = [
'page', 'revision', 'ip_changes', 'revision_comment_temp', 'comment', 'archive',
- 'revision_actor_temp', 'slots', 'content', 'content_models', 'slot_roles',
+ 'revision_actor_temp', 'slots', 'content',
];
$coreDBDataTables = array_merge( $userTables, $pageTables );
}
}
- $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
foreach ( $tablesUsed as $tbl ) {
- if ( !$db->tableExists( $tbl ) ) {
- continue;
- }
-
- if ( $truncate ) {
- $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tbl ), __METHOD__ );
- } else {
- $db->delete( $tbl, '*', __METHOD__ );
- }
-
- if ( in_array( $db->getType(), [ 'postgres', 'sqlite' ], true ) ) {
- // Reset the table's sequence too.
- $db->resetSequenceForTable( $tbl, __METHOD__ );
- }
-
- if ( $tbl === 'interwiki' ) {
- if ( !$this->interwikiTable ) {
- // @todo We should probably throw here, but this causes test failures that I
- // can't figure out, so for now we silently continue.
- continue;
- }
- $db->insert(
- 'interwiki',
- array_values( array_map( 'get_object_vars', iterator_to_array( $this->interwikiTable ) ) ),
- __METHOD__
- );
- }
-
- if ( $tbl === 'page' ) {
- // Forget about the pages since they don't
- // exist in the DB.
- MediaWikiServices::getInstance()->getLinkCache()->clear();
- }
+ $this->truncateTable( $tbl, $db );
}
if ( array_intersect( $tablesUsed, $coreDBDataTables ) ) {
}
}
+ /**
+ * Empties the given table and resets any auto-increment counters.
+ * Will also purge caches associated with some well known tables.
+ * If the table is not know, this method just returns.
+ *
+ * @param string $tableName
+ * @param IDatabase|null $db
+ */
+ protected function truncateTable( $tableName, IDatabase $db = null ) {
+ if ( !$db ) {
+ $db = $this->db;
+ }
+
+ if ( !$db->tableExists( $tableName ) ) {
+ return;
+ }
+
+ $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
+
+ if ( $truncate ) {
+ $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tableName ), __METHOD__ );
+ } else {
+ $db->delete( $tableName, '*', __METHOD__ );
+ }
+
+ if ( in_array( $db->getType(), [ 'postgres', 'sqlite' ], true ) ) {
+ // Reset the table's sequence too.
+ $db->resetSequenceForTable( $tableName, __METHOD__ );
+ }
+
+ if ( $tableName === 'interwiki' ) {
+ if ( !$this->interwikiTable ) {
+ // @todo We should probably throw here, but this causes test failures that I
+ // can't figure out, so for now we silently continue.
+ return;
+ }
+ $db->insert(
+ 'interwiki',
+ array_values( array_map( 'get_object_vars', iterator_to_array( $this->interwikiTable ) ) ),
+ __METHOD__
+ );
+ }
+
+ if ( $tableName === 'page' ) {
+ // Forget about the pages since they don't
+ // exist in the DB.
+ MediaWikiServices::getInstance()->getLinkCache()->clear();
+ }
+ }
+
private static function unprefixTable( &$tableName, $ind, $prefix ) {
$tableName = substr( $tableName, strlen( $prefix ) );
}
] );
}
- /**
- * @dataProvider providePreloadLinkHeaders
- * @covers OutputPage::addLogoPreloadLinkHeaders
- * @covers ResourceLoaderSkinModule::getLogo
- */
- public function testPreloadLinkHeaders( $config, $result, $baseDir = null ) {
- if ( $baseDir ) {
- $this->setMwGlobals( 'IP', $baseDir );
- }
- $out = TestingAccessWrapper::newFromObject( $this->newInstance( $config ) );
- $out->addLogoPreloadLinkHeaders();
-
- $this->assertEquals( $result, $out->getLinkHeader() );
- }
-
- public function providePreloadLinkHeaders() {
- return [
- [
- [
- 'ResourceBasePath' => '/w',
- 'Logo' => '/img/default.png',
- 'LogoHD' => [
- '1.5x' => '/img/one-point-five.png',
- '2x' => '/img/two-x.png',
- ],
- ],
- 'Link: </img/default.png>;rel=preload;as=image;media=' .
- 'not all and (min-resolution: 1.5dppx),' .
- '</img/one-point-five.png>;rel=preload;as=image;media=' .
- '(min-resolution: 1.5dppx) and (max-resolution: 1.999999dppx),' .
- '</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
- ],
- [
- [
- 'ResourceBasePath' => '/w',
- 'Logo' => '/img/default.png',
- 'LogoHD' => false,
- ],
- 'Link: </img/default.png>;rel=preload;as=image'
- ],
- [
- [
- 'ResourceBasePath' => '/w',
- 'Logo' => '/img/default.png',
- 'LogoHD' => [
- '2x' => '/img/two-x.png',
- ],
- ],
- 'Link: </img/default.png>;rel=preload;as=image;media=' .
- 'not all and (min-resolution: 2dppx),' .
- '</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
- ],
- [
- [
- 'ResourceBasePath' => '/w',
- 'Logo' => '/img/default.png',
- 'LogoHD' => [
- 'svg' => '/img/vector.svg',
- ],
- ],
- 'Link: </img/vector.svg>;rel=preload;as=image'
-
- ],
- [
- [
- 'ResourceBasePath' => '/w',
- 'Logo' => '/w/test.jpg',
- 'LogoHD' => false,
- 'UploadPath' => '/w/images',
- ],
- 'Link: </w/test.jpg?edcf2>;rel=preload;as=image',
- 'baseDir' => dirname( __DIR__ ) . '/data/media',
- ],
- ];
- }
-
/**
* @return OutputPage
*/
}
public function provideGetSlotsQueryInfo() {
- yield [
+ yield 'no options' => [
[],
+ [
+ 'tables' => [
+ 'slots'
+ ],
+ 'fields' => [
+ 'slot_revision_id',
+ 'slot_content_id',
+ 'slot_origin',
+ 'slot_role_id',
+ ],
+ 'joins' => [],
+ ]
+ ];
+ yield 'role option' => [
+ [ 'role' ],
[
'tables' => [
'slots',
'slot_roles',
],
- 'fields' => array_merge(
- [
- 'slot_revision_id',
- 'slot_content_id',
- 'slot_origin',
- 'role_name',
- ]
- ),
+ 'fields' => [
+ 'slot_revision_id',
+ 'slot_content_id',
+ 'slot_origin',
+ 'slot_role_id',
+ 'role_name',
+ ],
'joins' => [
- 'slot_roles' => [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ],
+ 'slot_roles' => [ 'LEFT JOIN', [ 'slot_role_id = role_id' ] ],
],
]
];
- yield [
+ yield 'content option' => [
[ 'content' ],
[
'tables' => [
'slots',
- 'slot_roles',
+ 'content',
+ ],
+ 'fields' => [
+ 'slot_revision_id',
+ 'slot_content_id',
+ 'slot_origin',
+ 'slot_role_id',
+ 'content_size',
+ 'content_sha1',
+ 'content_address',
+ 'content_model',
+ ],
+ 'joins' => [
+ 'content' => [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ],
+ ],
+ ]
+ ];
+ yield 'content and model options' => [
+ [ 'content', 'model' ],
+ [
+ 'tables' => [
+ 'slots',
'content',
'content_models',
],
- 'fields' => array_merge(
- [
- 'slot_revision_id',
- 'slot_content_id',
- 'slot_origin',
- 'role_name',
- 'content_size',
- 'content_sha1',
- 'content_address',
- 'model_name',
- ]
- ),
+ 'fields' => [
+ 'slot_revision_id',
+ 'slot_content_id',
+ 'slot_origin',
+ 'slot_role_id',
+ 'content_size',
+ 'content_sha1',
+ 'content_address',
+ 'content_model',
+ 'model_name',
+ ],
'joins' => [
- 'slot_roles' => [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ],
'content' => [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ],
- 'content_models' => [ 'INNER JOIN', [ 'content_model = model_id' ] ],
+ 'content_models' => [ 'LEFT JOIN', [ 'content_model = model_id' ] ],
],
]
];
use CommentStoreComment;
use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\MutableRevisionRecord;
use MediaWiki\Storage\RevisionRecord;
use MediaWiki\Storage\SlotRecord;
use TextContent;
}
public function provideGetSlotsQueryInfo() {
- yield [
+ yield 'no options' => [
[],
+ [
+ 'tables' => [
+ 'slots'
+ ],
+ 'fields' => [
+ 'slot_revision_id',
+ 'slot_content_id',
+ 'slot_origin',
+ 'slot_role_id',
+ ],
+ 'joins' => [],
+ ]
+ ];
+ yield 'role option' => [
+ [ 'role' ],
[
'tables' => [
'slots',
'slot_roles',
],
- 'fields' => array_merge(
- [
- 'slot_revision_id',
- 'slot_content_id',
- 'slot_origin',
- 'role_name',
- ]
- ),
+ 'fields' => [
+ 'slot_revision_id',
+ 'slot_content_id',
+ 'slot_origin',
+ 'slot_role_id',
+ 'role_name',
+ ],
'joins' => [
- 'slot_roles' => [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ],
+ 'slot_roles' => [ 'LEFT JOIN', [ 'slot_role_id = role_id' ] ],
],
]
];
- yield [
+ yield 'content option' => [
[ 'content' ],
[
'tables' => [
'slots',
- 'slot_roles',
+ 'content',
+ ],
+ 'fields' => [
+ 'slot_revision_id',
+ 'slot_content_id',
+ 'slot_origin',
+ 'slot_role_id',
+ 'content_size',
+ 'content_sha1',
+ 'content_address',
+ 'content_model',
+ ],
+ 'joins' => [
+ 'content' => [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ],
+ ],
+ ]
+ ];
+ yield 'content and model options' => [
+ [ 'content', 'model' ],
+ [
+ 'tables' => [
+ 'slots',
'content',
'content_models',
],
- 'fields' => array_merge(
- [
- 'slot_revision_id',
- 'slot_content_id',
- 'slot_origin',
- 'role_name',
- 'content_size',
- 'content_sha1',
- 'content_address',
- 'model_name',
- ]
- ),
+ 'fields' => [
+ 'slot_revision_id',
+ 'slot_content_id',
+ 'slot_origin',
+ 'slot_role_id',
+ 'content_size',
+ 'content_sha1',
+ 'content_address',
+ 'content_model',
+ 'model_name',
+ ],
'joins' => [
- 'slot_roles' => [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ],
'content' => [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ],
- 'content_models' => [ 'INNER JOIN', [ 'content_model = model_id' ] ],
+ 'content_models' => [ 'LEFT JOIN', [ 'content_model = model_id' ] ],
],
]
];
}
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
+ * @covers \MediaWiki\Storage\RevisionStore::insertSlotRowOn
+ * @covers \MediaWiki\Storage\RevisionStore::insertContentRowOn
+ */
+ public function testInsertRevisionOn_T202032() {
+ // This test only makes sense for MySQL
+ if ( $this->db->getType() !== 'mysql' ) {
+ $this->assertTrue( true );
+ return;
+ }
+
+ // NOTE: must be done before checking MAX(rev_id)
+ $page = $this->getTestPage();
+
+ $maxRevId = $this->db->selectField( 'revision', 'MAX(rev_id)' );
+
+ // Construct a slot row that will conflict with the insertion of the next revision ID,
+ // to emulate the failure mode described in T202032. Nothing will ever read this row,
+ // we just need it to trigger a primary key conflict.
+ $this->db->insert( 'slots', [
+ 'slot_revision_id' => $maxRevId + 1,
+ 'slot_role_id' => 1,
+ 'slot_content_id' => 0,
+ 'slot_origin' => 0
+ ], __METHOD__ );
+
+ $rev = new MutableRevisionRecord( $page->getTitle() );
+ $rev->setTimestamp( '20180101000000' );
+ $rev->setComment( CommentStoreComment::newUnsavedComment( 'test' ) );
+ $rev->setUser( $this->getTestUser()->getUser() );
+ $rev->setContent( 'main', new WikitextContent( 'Text' ) );
+ $rev->setPageId( $page->getId() );
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $return = $store->insertRevisionOn( $rev, $this->db );
+
+ $this->assertSame( $maxRevId + 2, $return->getId() );
+
+ // is the new revision correct?
+ $this->assertRevisionCompleteness( $return );
+ $this->assertRevisionRecordsEqual( $rev, $return );
+
+ // can we find it directly in the database?
+ $this->assertRevisionExistsInDatabase( $return );
+
+ // can we load it from the store?
+ $loaded = $store->getRevisionById( $return->getId() );
+ $this->assertRevisionCompleteness( $loaded );
+ $this->assertRevisionRecordsEqual( $return, $loaded );
+ }
+
}
parent::setUp();
}
+ protected function addCoreDBData() {
+ // The default implementation causes the slot_roles to already have content. Skip that.
+ }
+
private function populateTable( $values ) {
$insertValues = [];
foreach ( $values as $name ) {
$name,
$expectedId
) {
+ // Make sure the table is empty!
+ $this->truncateTable( 'slot_roles' );
+
$this->populateTable( $existingValues );
$store = $this->getNameTableSqlStore( $cacheBag, (int)$needsInsert, $selectCalls );
$this->assertSame( $expected, TestingAccessWrapper::newFromObject( $store )->tableCache );
}
+ public function testReloadMap() {
+ $this->populateTable( [ 'foo' ] );
+ $store = $this->getNameTableSqlStore( new HashBagOStuff(), 0, 2 );
+
+ // force load
+ $this->assertCount( 1, $store->getMap() );
+
+ // add more stuff to the table, so the cache gets out of sync
+ $this->populateTable( [ 'bar' ] );
+
+ $expected = [ 1 => 'foo', 2 => 'bar' ];
+ $this->assertSame( $expected, $store->reloadMap() );
+ $this->assertSame( $expected, $store->getMap() );
+ }
+
public function testCacheRaceCondition() {
$wanHashBag = new HashBagOStuff();
$store1 = $this->getNameTableSqlStore( $wanHashBag, 1, 1 );
*/
class PageUpdaterTest extends MediaWikiTestCase {
+ public static function setUpBeforeClass() {
+ parent::setUpBeforeClass();
+
+ // force service reset!
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'RevisionStore' );
+ }
+
private function getDummyTitle( $method ) {
return Title::newFromText( $method, $this->getDefaultWikitextNS() );
}
// check site stats - this asserts that derived data updates where run.
$stats = $this->db->selectRow( 'site_stats', '*', '1=1' );
+ $this->assertNotNull( $stats, 'site_stats' );
$this->assertSame( $oldStats->ss_total_pages + 0, (int)$stats->ss_total_pages );
$this->assertSame( $oldStats->ss_total_edits + 2, (int)$stats->ss_total_edits );
}
$this->tablesUsed += $this->getMcrTablesToReset();
- $this->setMwGlobals(
- 'wgMultiContentRevisionSchemaMigrationStage',
- $this->getMcrMigrationStage()
- );
-
- $this->setMwGlobals(
- 'wgContentHandlerUseDB',
- $this->getContentHandlerUseDB()
- );
+ $this->setMwGlobals( [
+ 'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
+ 'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
+ 'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ ] );
$this->overrideMwServices();
}
$this->assertSame( 0, $count );
}
- private function assertLinkTargetsEqual( LinkTarget $l1, LinkTarget $l2 ) {
+ protected function assertLinkTargetsEqual( LinkTarget $l1, LinkTarget $l2 ) {
$this->assertEquals( $l1->getDBkey(), $l2->getDBkey() );
$this->assertEquals( $l1->getNamespace(), $l2->getNamespace() );
$this->assertEquals( $l1->getFragment(), $l2->getFragment() );
$this->assertEquals( $l1->getInterwiki(), $l2->getInterwiki() );
}
- private function assertRevisionRecordsEqual( RevisionRecord $r1, RevisionRecord $r2 ) {
+ protected function assertRevisionRecordsEqual( RevisionRecord $r1, RevisionRecord $r2 ) {
$this->assertEquals(
$r1->getPageAsLinkTarget()->getNamespace(),
$r2->getPageAsLinkTarget()->getNamespace()
}
}
- private function assertSlotRecordsEqual( SlotRecord $s1, SlotRecord $s2 ) {
+ protected function assertSlotRecordsEqual( SlotRecord $s1, SlotRecord $s2 ) {
$this->assertSame( $s1->getRole(), $s2->getRole() );
$this->assertSame( $s1->getModel(), $s2->getModel() );
$this->assertSame( $s1->getFormat(), $s2->getFormat() );
$s1->hasAddress() ? $this->assertSame( $s1->hasAddress(), $s2->hasAddress() ) : null;
}
- private function assertRevisionCompleteness( RevisionRecord $r ) {
+ protected function assertRevisionCompleteness( RevisionRecord $r ) {
$this->assertTrue( $r->hasSlot( 'main' ) );
$this->assertInstanceOf( SlotRecord::class, $r->getSlot( 'main' ) );
$this->assertInstanceOf( Content::class, $r->getContent( 'main' ) );
}
}
- private function assertSlotCompleteness( RevisionRecord $r, SlotRecord $slot ) {
+ protected function assertSlotCompleteness( RevisionRecord $r, SlotRecord $slot ) {
$this->assertTrue( $slot->hasAddress() );
$this->assertSame( $r->getId(), $slot->getRevision() );
$this->doBlock( [ 'tags' => 'custom tag' ] );
$dbw = wfGetDB( DB_MASTER );
- $this->assertSame( 'custom tag', $dbw->selectField(
+ $this->assertSame( 1, (int)$dbw->selectField(
[ 'change_tag', 'logging' ],
- 'ct_tag',
- [ 'log_type' => 'block' ],
+ 'COUNT(*)',
+ [ 'log_type' => 'block', 'ct_tag' => 'custom tag' ],
__METHOD__,
[],
[ 'change_tag' => [ 'INNER JOIN', 'ct_log_id = log_id' ] ]
self::$repl['revF1'] = $this->addPage( 'F', "== Section 1 ==\nF 1.1\n\n== Section 2 ==\nF 1.2" );
self::$repl['pageF'] = Title::newFromText( 'ApiComparePagesTest F' )->getArticleId();
+ self::$repl['revG1'] = $this->addPage( 'G', "== Section 1 ==\nG 1.1", CONTENT_MODEL_TEXT );
+ self::$repl['pageG'] = Title::newFromText( 'ApiComparePagesTest G' )->getArticleId();
+
WikiPage::factory( Title::newFromText( 'ApiComparePagesTest C' ) )
->doDeleteArticleReal( 'Test for ApiComparePagesTest' );
$params += [
'action' => 'compare',
+ 'errorformat' => 'none',
];
$user = $sysop
}
}
+ private static function makeDeprecationWarnings( ...$params ) {
+ $warn = [];
+ foreach ( $params as $p ) {
+ $warn[] = [
+ 'code' => 'deprecation',
+ 'data' => [ 'feature' => "action=compare&{$p}" ],
+ 'module' => 'compare',
+ ];
+ if ( count( $warn ) === 1 ) {
+ $warn[] = [
+ 'code' => 'deprecation-help',
+ 'module' => 'main',
+ ];
+ }
+ }
+
+ return $warn;
+ }
+
public static function provideDiff() {
// phpcs:disable Generic.Files.LineLength.TooLong
return [
],
'Basic diff, text' => [
[
- 'fromtext' => 'From text',
- 'fromcontentmodel' => 'wikitext',
- 'totext' => 'To text {{subst:PAGENAME}}',
- 'tocontentmodel' => 'wikitext',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
+ 'fromcontentmodel-main' => 'wikitext',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text {{subst:PAGENAME}}',
+ 'tocontentmodel-main' => 'wikitext',
],
[
'compare' => [
],
'Basic diff, text 2' => [
[
- 'fromtext' => 'From text',
- 'totext' => 'To text {{subst:PAGENAME}}',
- 'tocontentmodel' => 'wikitext',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text {{subst:PAGENAME}}',
+ 'tocontentmodel-main' => 'wikitext',
],
[
'compare' => [
],
'Basic diff, guessed model' => [
[
- 'fromtext' => 'From text',
- 'totext' => 'To text',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text',
],
[
- 'warnings' => [
- 'compare' => [
- 'warnings' => 'No content model could be determined, assuming wikitext.',
- ],
- ],
+ 'warnings' => [ [ 'code' => 'compare-nocontentmodel', 'module' => 'compare' ] ],
'compare' => [
'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
],
'Basic diff, text with title and PST' => [
[
- 'fromtext' => 'From text',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
'totitle' => 'Test',
- 'totext' => 'To text {{subst:PAGENAME}}',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text {{subst:PAGENAME}}',
'topst' => true,
],
[
],
'Basic diff, text with page ID and PST' => [
[
- 'fromtext' => 'From text',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
'toid' => '{{REPL:pageB}}',
- 'totext' => 'To text {{subst:PAGENAME}}',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text {{subst:PAGENAME}}',
'topst' => true,
],
[
],
'Basic diff, text with revision and PST' => [
[
- 'fromtext' => 'From text',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
'torev' => '{{REPL:revB2}}',
- 'totext' => 'To text {{subst:PAGENAME}}',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text {{subst:PAGENAME}}',
'topst' => true,
],
[
],
'Basic diff, text with deleted revision and PST' => [
[
- 'fromtext' => 'From text',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
'torev' => '{{REPL:revC2}}',
- 'totext' => 'To text {{subst:PAGENAME}}',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text {{subst:PAGENAME}}',
'topst' => true,
],
[
'Basic diff, test with sections' => [
[
'fromtitle' => 'ApiComparePagesTest F',
- 'fromsection' => 1,
- 'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
- 'tosection' => 2,
+ 'fromslots' => 'main',
+ 'fromtext-main' => "== Section 2 ==\nFrom text?",
+ 'fromsection-main' => 2,
+ 'totitle' => 'ApiComparePagesTest F',
+ 'toslots' => 'main',
+ 'totext-main' => "== Section 1 ==\nTo text?",
+ 'tosection-main' => 1,
],
[
'compare' => [
'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
- . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>== Section <del class="diffchange diffchange-inline">1 </del>==</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>== Section <ins class="diffchange diffchange-inline">2 </ins>==</div></td></tr>' . "\n"
- . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n",
- 'fromid' => '{{REPL:pageF}}',
- 'fromrevid' => '{{REPL:revF1}}',
- 'fromns' => '0',
- 'fromtitle' => 'ApiComparePagesTest F',
+ . '<tr><td class=\'diff-marker\'> </td><td class=\'diff-context\'><div>== Section 1 ==</div></td><td class=\'diff-marker\'> </td><td class=\'diff-context\'><div>== Section 1 ==</div></td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'> </td><td class=\'diff-context\'></td><td class=\'diff-marker\'> </td><td class=\'diff-context\'></td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'> </td><td class=\'diff-context\'><div>== Section 2 ==</div></td><td class=\'diff-marker\'> </td><td class=\'diff-context\'><div>== Section 2 ==</div></td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From text?</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">F 1.2</ins></div></td></tr>' . "\n",
]
],
],
]
],
],
+ 'Diff for specific slots' => [
+ // @todo Use a page with multiple slots here
+ [
+ 'fromrev' => '{{REPL:revA1}}',
+ 'torev' => '{{REPL:revA3}}',
+ 'prop' => 'diff',
+ 'slots' => 'main',
+ ],
+ [
+ 'compare' => [
+ 'bodies' => [
+ 'main' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>A <del class="diffchange diffchange-inline">1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>A <ins class="diffchange diffchange-inline">3</ins></div></td></tr>' . "\n",
+ ],
+ ],
+ ],
+ ],
+ // @todo Add a test for diffing with a deleted slot. Deleting 'main' doesn't work.
+
+ 'Basic diff, deprecated text' => [
+ [
+ 'fromtext' => 'From text',
+ 'fromcontentmodel' => 'wikitext',
+ 'totext' => 'To text {{subst:PAGENAME}}',
+ 'tocontentmodel' => 'wikitext',
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromtext', 'fromcontentmodel', 'totext', 'tocontentmodel' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">{{subst:PAGENAME}}</ins></div></td></tr>' . "\n",
+ ]
+ ],
+ ],
+ 'Basic diff, deprecated text 2' => [
+ [
+ 'fromtext' => 'From text',
+ 'totext' => 'To text {{subst:PAGENAME}}',
+ 'tocontentmodel' => 'wikitext',
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext', 'tocontentmodel' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">{{subst:PAGENAME}}</ins></div></td></tr>' . "\n",
+ ]
+ ],
+ ],
+ 'Basic diff, deprecated text, guessed model' => [
+ [
+ 'fromtext' => 'From text',
+ 'totext' => 'To text',
+ ],
+ [
+ 'warnings' => array_merge( self::makeDeprecationWarnings( 'fromtext', 'totext' ), [
+ [ 'code' => 'compare-nocontentmodel', 'module' => 'compare' ],
+ ] ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text</div></td></tr>' . "\n",
+ ]
+ ],
+ ],
+ 'Basic diff, deprecated text with title and PST' => [
+ [
+ 'fromtext' => 'From text',
+ 'totitle' => 'Test',
+ 'totext' => 'To text {{subst:PAGENAME}}',
+ 'topst' => true,
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">Test</ins></div></td></tr>' . "\n",
+ ]
+ ],
+ ],
+ 'Basic diff, deprecated text with page ID and PST' => [
+ [
+ 'fromtext' => 'From text',
+ 'toid' => '{{REPL:pageB}}',
+ 'totext' => 'To text {{subst:PAGENAME}}',
+ 'topst' => true,
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest B</ins></div></td></tr>' . "\n",
+ ]
+ ],
+ ],
+ 'Basic diff, deprecated text with revision and PST' => [
+ [
+ 'fromtext' => 'From text',
+ 'torev' => '{{REPL:revB2}}',
+ 'totext' => 'To text {{subst:PAGENAME}}',
+ 'topst' => true,
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest B</ins></div></td></tr>' . "\n",
+ ]
+ ],
+ ],
+ 'Basic diff, deprecated text with deleted revision and PST' => [
+ [
+ 'fromtext' => 'From text',
+ 'torev' => '{{REPL:revC2}}',
+ 'totext' => 'To text {{subst:PAGENAME}}',
+ 'topst' => true,
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest C</ins></div></td></tr>' . "\n",
+ ]
+ ],
+ false, true
+ ],
+ 'Basic diff, test with deprecated sections' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest F',
+ 'fromsection' => 1,
+ 'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
+ 'tosection' => 2,
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromsection', 'totext', 'tosection' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>== Section <del class="diffchange diffchange-inline">1 </del>==</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>== Section <ins class="diffchange diffchange-inline">2 </ins>==</div></td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n",
+ 'fromid' => '{{REPL:pageF}}',
+ 'fromrevid' => '{{REPL:revF1}}',
+ 'fromns' => '0',
+ 'fromtitle' => 'ApiComparePagesTest F',
+ ]
+ ],
+ ],
+ 'Basic diff, test with deprecated sections and revdel, non-sysop' => [
+ [
+ 'fromrev' => '{{REPL:revB2}}',
+ 'fromsection' => 0,
+ 'torev' => '{{REPL:revB4}}',
+ 'tosection' => 0,
+ ],
+ [],
+ 'missingcontent'
+ ],
+ 'Basic diff, test with deprecated sections and revdel, sysop' => [
+ [
+ 'fromrev' => '{{REPL:revB2}}',
+ 'fromsection' => 0,
+ 'torev' => '{{REPL:revB4}}',
+ 'tosection' => 0,
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromsection', 'tosection' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>B <del class="diffchange diffchange-inline">2</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>B <ins class="diffchange diffchange-inline">4</ins></div></td></tr>' . "\n",
+ 'fromid' => '{{REPL:pageB}}',
+ 'fromrevid' => '{{REPL:revB2}}',
+ 'fromns' => 0,
+ 'fromtitle' => 'ApiComparePagesTest B',
+ 'fromtexthidden' => true,
+ 'fromuserhidden' => true,
+ 'fromcommenthidden' => true,
+ 'toid' => '{{REPL:pageB}}',
+ 'torevid' => '{{REPL:revB4}}',
+ 'tons' => 0,
+ 'totitle' => 'ApiComparePagesTest B',
+ ]
+ ],
+ false, true,
+ ],
'Error, missing title' => [
[
[],
'missingcontent'
],
+ 'Error, Relative diff, no prev' => [
+ [
+ 'fromrev' => '{{REPL:revA1}}',
+ 'torelative' => 'prev',
+ 'prop' => 'ids',
+ ],
+ [],
+ 'baddiff'
+ ],
+ 'Error, Relative diff, no next' => [
+ [
+ 'fromrev' => '{{REPL:revA4}}',
+ 'torelative' => 'next',
+ 'prop' => 'ids',
+ ],
+ [],
+ 'baddiff'
+ ],
+ 'Error, section diff with no revision' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest F',
+ 'toslots' => 'main',
+ 'totext-main' => "== Section 1 ==\nTo text?",
+ 'tosection-main' => 1,
+ ],
+ [],
+ 'compare-notorevision',
+ ],
+ 'Error, section diff with revdeleted revision' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest F',
+ 'torev' => '{{REPL:revB2}}',
+ 'toslots' => 'main',
+ 'totext-main' => "== Section 1 ==\nTo text?",
+ 'tosection-main' => 1,
+ ],
+ [],
+ 'missingcontent',
+ ],
+ 'Error, section diff with a content model not supporting sections' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest G',
+ 'torev' => '{{REPL:revG1}}',
+ 'toslots' => 'main',
+ 'totext-main' => "== Section 1 ==\nTo text?",
+ 'tosection-main' => 1,
+ ],
+ [],
+ 'sectionsnotsupported',
+ ],
+ 'Error, section diff with bad content model' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest F',
+ 'torev' => '{{REPL:revF1}}',
+ 'toslots' => 'main',
+ 'totext-main' => "== Section 1 ==\nTo text?",
+ 'tosection-main' => 1,
+ 'tocontentmodel-main' => CONTENT_MODEL_TEXT,
+ ],
+ [],
+ 'sectionreplacefailed',
+ ],
];
// phpcs:enable
}
public function testLoadRevisionData() {
$cases = $this->getLoadRevisionDataCases();
- foreach ( $cases as $case ) {
- list( $expectedOld, $expectedNew, $old, $new, $message ) = $case;
+ foreach ( $cases as $testName => $case ) {
+ list( $expectedOld, $expectedNew, $expectedRet, $old, $new ) = $case;
$diffEngine = new DifferenceEngine( $this->context, $old, $new, 2, true, false );
- $diffEngine->loadRevisionData();
+ $ret = $diffEngine->loadRevisionData();
+ $ret2 = $diffEngine->loadRevisionData();
- $this->assertEquals( $diffEngine->getOldid(), $expectedOld, $message );
- $this->assertEquals( $diffEngine->getNewid(), $expectedNew, $message );
+ $this->assertEquals( $expectedOld, $diffEngine->getOldid(), $testName );
+ $this->assertEquals( $expectedNew, $diffEngine->getNewid(), $testName );
+ $this->assertEquals( $expectedRet, $ret, $testName );
+ $this->assertEquals( $expectedRet, $ret2, $testName );
}
}
$revs = self::$revisions;
return [
- [ $revs[2], $revs[3], $revs[3], 'prev', 'diff=prev' ],
- [ $revs[2], $revs[3], $revs[2], 'next', 'diff=next' ],
- [ $revs[1], $revs[3], $revs[1], $revs[3], 'diff=' . $revs[3] ],
- [ $revs[1], $revs[3], $revs[1], 0, 'diff=0' ]
+ 'diff=prev' => [ $revs[2], $revs[3], true, $revs[3], 'prev' ],
+ 'diff=next' => [ $revs[2], $revs[3], true, $revs[2], 'next' ],
+ 'diff=' . $revs[3] => [ $revs[1], $revs[3], true, $revs[1], $revs[3] ],
+ 'diff=0' => [ $revs[1], $revs[3], true, $revs[1], 0 ],
+ 'diff=prev&oldid=<first>' => [ false, $revs[0], true, $revs[0], 'prev' ],
+ 'invalid' => [ 123456789, $revs[1], false, 123456789, $revs[1] ],
];
}
'foo' => 'bar',
'baz' => 'rawr',
"they're" => '"quoted properly"',
+ 'nested' => [ 'elements', 'work' ],
+ 'and' => [ 'these' => 'do too' ],
];
$writer = new StaticArrayWriter();
$actual = $writer->create( $data, "Header\nWith\nNewlines" );
'foo' => 'bar',
'baz' => 'rawr',
'they\'re' => '"quoted properly"',
+ 'nested' => [
+ 0 => 'elements',
+ 1 => 'work',
+ ],
+ 'and' => [
+ 'these' => 'do too',
+ ],
];
PHP;
--- /dev/null
+<?php
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\MutableRevisionRecord;
+use MediaWiki\Storage\RevisionRecord;
+use PHPUnit\Framework\MockObject\MockObject;
+
+/**
+ * @covers \Article::view()
+ */
+class ArticleViewTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setUserLang( 'qqx' );
+ }
+
+ private function getHtml( OutputPage $output ) {
+ return preg_replace( '/<!--.*?-->/s', '', $output->getHTML() );
+ }
+
+ /**
+ * @param string|Title $title
+ * @param Content[]|string[] $revisionContents Content of the revisions to create
+ * (as Content or string).
+ * @param RevisionRecord[] &$revisions will be filled with the RevisionRecord for $content.
+ *
+ * @return WikiPage
+ * @throws MWException
+ */
+ private function getPage( $title, array $revisionContents = [], array &$revisions = [] ) {
+ if ( is_string( $title ) ) {
+ $title = Title::makeTitle( $this->getDefaultWikitextNS(), $title );
+ }
+
+ $page = WikiPage::factory( $title );
+
+ $user = $this->getTestUser()->getUser();
+
+ foreach ( $revisionContents as $key => $cont ) {
+ if ( is_string( $cont ) ) {
+ $cont = new WikitextContent( $cont );
+ }
+
+ $u = $page->newPageUpdater( $user );
+ $u->setContent( 'main', $cont );
+ $rev = $u->saveRevision( CommentStoreComment::newUnsavedComment( 'Rev ' . $key ) );
+
+ $revisions[ $key ] = $rev;
+ }
+
+ return $page;
+ }
+
+ /**
+ * @covers Article::getOldId()
+ * @covers Article::getRevIdFetched()
+ */
+ public function testGetOldId() {
+ $revisions = [];
+ $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
+
+ $idA = $revisions[1]->getId();
+ $idB = $revisions[2]->getId();
+
+ // oldid in constructor
+ $article = new Article( $page->getTitle(), $idA );
+ $this->assertSame( $idA, $article->getOldID() );
+ $article->getRevisionFetched();
+ $this->assertSame( $idA, $article->getRevIdFetched() );
+
+ // oldid 0 in constructor
+ $article = new Article( $page->getTitle(), 0 );
+ $this->assertSame( 0, $article->getOldID() );
+ $article->getRevisionFetched();
+ $this->assertSame( $idB, $article->getRevIdFetched() );
+
+ // oldid in request
+ $article = new Article( $page->getTitle() );
+ $context = new RequestContext();
+ $context->setRequest( new FauxRequest( [ 'oldid' => $idA ] ) );
+ $article->setContext( $context );
+ $this->assertSame( $idA, $article->getOldID() );
+ $article->getRevisionFetched();
+ $this->assertSame( $idA, $article->getRevIdFetched() );
+
+ // no oldid
+ $article = new Article( $page->getTitle() );
+ $context = new RequestContext();
+ $context->setRequest( new FauxRequest( [] ) );
+ $article->setContext( $context );
+ $this->assertSame( 0, $article->getOldID() );
+ $article->getRevisionFetched();
+ $this->assertSame( $idB, $article->getRevIdFetched() );
+ }
+
+ public function testView() {
+ $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ] );
+
+ $article = new Article( $page->getTitle(), 0 );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( 'Test B', $this->getHtml( $output ) );
+ $this->assertNotContains( 'id="mw-revision-info"', $this->getHtml( $output ) );
+ $this->assertNotContains( 'id="mw-revision-nav"', $this->getHtml( $output ) );
+ }
+
+ public function testViewCached() {
+ $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ] );
+
+ $po = new ParserOutput( 'Cached Text' );
+
+ $article = new Article( $page->getTitle(), 0 );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+
+ $cache = MediaWikiServices::getInstance()->getParserCache();
+ $cache->save( $po, $page, $article->getParserOptions() );
+
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( 'Cached Text', $this->getHtml( $output ) );
+ $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
+ $this->assertNotContains( 'Test B', $this->getHtml( $output ) );
+ }
+
+ /**
+ * @covers Article::getRedirectTarget()
+ */
+ public function testViewRedirect() {
+ $target = Title::makeTitle( $this->getDefaultWikitextNS(), 'Test_Target' );
+ $redirectText = '#REDIRECT [[' . $target->getPrefixedText() . ']]';
+
+ $page = $this->getPage( __METHOD__, [ $redirectText ] );
+
+ $article = new Article( $page->getTitle(), 0 );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+ $article->view();
+
+ $this->assertNotNull(
+ $article->getRedirectTarget()->getPrefixedDBkey()
+ );
+ $this->assertSame(
+ $target->getPrefixedDBkey(),
+ $article->getRedirectTarget()->getPrefixedDBkey()
+ );
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( 'class="redirectText"', $this->getHtml( $output ) );
+ $this->assertContains(
+ '>' . htmlspecialchars( $target->getPrefixedText() ) . '<',
+ $this->getHtml( $output )
+ );
+ }
+
+ public function testViewNonText() {
+ $dummy = $this->getPage( __METHOD__, [ 'Dummy' ] );
+ $dummyRev = $dummy->getRevision()->getRevisionRecord();
+ $title = $dummy->getTitle();
+
+ /** @var MockObject|ContentHandler $mockHandler */
+ $mockHandler = $this->getMockBuilder( ContentHandler::class )
+ ->setMethods(
+ [
+ 'isParserCacheSupported',
+ 'serializeContent',
+ 'unserializeContent',
+ 'makeEmptyContent',
+ ]
+ )
+ ->setConstructorArgs( [ 'NotText', [ 'application/frobnitz' ] ] )
+ ->getMock();
+
+ $mockHandler->method( 'isParserCacheSupported' )
+ ->willReturn( false );
+
+ $this->setTemporaryHook(
+ 'ContentHandlerForModelID',
+ function ( $id, &$handler ) use ( $mockHandler ) {
+ $handler = $mockHandler;
+ }
+ );
+
+ /** @var MockObject|Content $content */
+ $content = $this->getMock( Content::class );
+ $content->method( 'getParserOutput' )
+ ->willReturn( new ParserOutput( 'Structured Output' ) );
+ $content->method( 'getModel' )
+ ->willReturn( 'NotText' );
+ $content->method( 'getNativeData' )
+ ->willReturn( [ (object)[ 'x' => 'stuff' ] ] );
+ $content->method( 'copy' )
+ ->willReturn( $content );
+
+ $rev = new MutableRevisionRecord( $title );
+ $rev->setId( $dummyRev->getId() );
+ $rev->setPageId( $title->getArticleID() );
+ $rev->setUser( $dummyRev->getUser() );
+ $rev->setComment( $dummyRev->getComment() );
+ $rev->setTimestamp( $dummyRev->getTimestamp() );
+
+ $rev->setContent( 'main', $content );
+
+ $rev = new Revision( $rev );
+
+ /** @var MockObject|WikiPage $page */
+ $page = $this->getMockBuilder( WikiPage::class )
+ ->setMethods( [ 'getRevision', 'getLatest' ] )
+ ->setConstructorArgs( [ $title ] )
+ ->getMock();
+
+ $page->method( 'getRevision' )
+ ->willReturn( $rev );
+ $page->method( 'getLatest' )
+ ->willReturn( $rev->getId() );
+
+ $article = Article::newFromWikiPage( $page, RequestContext::getMain() );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( 'Structured Output', $this->getHtml( $output ) );
+ $this->assertNotContains( 'Dummy', $this->getHtml( $output ) );
+ }
+
+ public function testViewOfOldRevision() {
+ $revisions = [];
+ $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
+ $idA = $revisions[1]->getId();
+
+ $article = new Article( $page->getTitle(), $idA );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( 'Test A', $this->getHtml( $output ) );
+ $this->assertContains( 'id="mw-revision-info"', $output->getSubtitle() );
+ $this->assertContains( 'id="mw-revision-nav"', $output->getSubtitle() );
+
+ $this->assertNotContains( 'id="revision-info-current"', $output->getSubtitle() );
+ $this->assertNotContains( 'Test B', $this->getHtml( $output ) );
+ }
+
+ public function testViewOfCurrentRevision() {
+ $revisions = [];
+ $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
+ $idB = $revisions[2]->getId();
+
+ $article = new Article( $page->getTitle(), $idB );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( 'Test B', $this->getHtml( $output ) );
+ $this->assertContains( 'id="mw-revision-info-current"', $output->getSubtitle() );
+ $this->assertContains( 'id="mw-revision-nav"', $output->getSubtitle() );
+ }
+
+ public function testViewOfMissingRevision() {
+ $revisions = [];
+ $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ], $revisions );
+ $badId = $revisions[1]->getId() + 100;
+
+ $article = new Article( $page->getTitle(), $badId );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( 'missing-revision: ' . $badId, $this->getHtml( $output ) );
+
+ $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
+ }
+
+ public function testViewOfDeletedRevision() {
+ $revisions = [];
+ $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
+ $idA = $revisions[1]->getId();
+
+ $revDelList = new RevDelRevisionList(
+ RequestContext::getMain(), $page->getTitle(), [ $idA ]
+ );
+ $revDelList->setVisibility( [
+ 'value' => [ RevisionRecord::DELETED_TEXT => 1 ],
+ 'comment' => "Testing",
+ ] );
+
+ $article = new Article( $page->getTitle(), $idA );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( '(rev-deleted-text-permission)', $this->getHtml( $output ) );
+
+ $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
+ $this->assertNotContains( 'Test B', $this->getHtml( $output ) );
+ }
+
+ public function testViewMissingPage() {
+ $page = $this->getPage( __METHOD__ );
+
+ $article = new Article( $page->getTitle() );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( '(noarticletextanon)', $this->getHtml( $output ) );
+ }
+
+ public function testViewDeletedPage() {
+ $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ] );
+ $page->doDeleteArticle( 'Test' );
+
+ $article = new Article( $page->getTitle() );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( 'moveddeleted', $this->getHtml( $output ) );
+ $this->assertContains( 'logentry-delete-delete', $this->getHtml( $output ) );
+ $this->assertContains( '(noarticletextanon)', $this->getHtml( $output ) );
+
+ $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
+ $this->assertNotContains( 'Test B', $this->getHtml( $output ) );
+ }
+
+ public function testViewMessagePage() {
+ $title = Title::makeTitle( NS_MEDIAWIKI, 'Mainpage' );
+ $page = $this->getPage( $title );
+
+ $article = new Article( $page->getTitle() );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains(
+ wfMessage( 'mainpage' )->inContentLanguage()->parse(),
+ $this->getHtml( $output )
+ );
+ $this->assertNotContains( '(noarticletextanon)', $this->getHtml( $output ) );
+ }
+
+ public function testViewMissingUserPage() {
+ $user = $this->getTestUser()->getUser();
+ $user->addToDatabase();
+
+ $title = Title::makeTitle( NS_USER, $user->getName() );
+
+ $page = $this->getPage( $title );
+
+ $article = new Article( $page->getTitle() );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( '(noarticletextanon)', $this->getHtml( $output ) );
+ $this->assertNotContains( '(userpage-userdoesnotexist-view)', $this->getHtml( $output ) );
+ }
+
+ public function testViewUserPageOfNonexistingUser() {
+ $user = User::newFromName( 'Testing ' . __METHOD__ );
+
+ $title = Title::makeTitle( NS_USER, $user->getName() );
+
+ $page = $this->getPage( $title );
+
+ $article = new Article( $page->getTitle() );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( '(noarticletextanon)', $this->getHtml( $output ) );
+ $this->assertContains( '(userpage-userdoesnotexist-view:', $this->getHtml( $output ) );
+ }
+
+ public function testArticleViewHeaderHook() {
+ $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ] );
+
+ $article = new Article( $page->getTitle(), 0 );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+
+ $this->setTemporaryHook(
+ 'ArticleViewHeader',
+ function ( Article $articlePage, &$outputDone, &$useParserCache ) use ( $article ) {
+ $this->assertSame( $article, $articlePage, '$articlePage' );
+
+ $outputDone = new ParserOutput( 'Hook Text' );
+ $outputDone->setTitleText( 'Hook Title' );
+
+ $articlePage->getContext()->getOutput()->addParserOutput( $outputDone );
+ }
+ );
+
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
+ $this->assertContains( 'Hook Text', $this->getHtml( $output ) );
+ $this->assertSame( 'Hook Title', $output->getPageTitle() );
+ }
+
+ public function testArticleContentViewCustomHook() {
+ $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ] );
+
+ $article = new Article( $page->getTitle(), 0 );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+
+ // use ArticleViewHeader hook to bypass the parser cache
+ $this->setTemporaryHook(
+ 'ArticleViewHeader',
+ function ( Article $articlePage, &$outputDone, &$useParserCache ) use ( $article ) {
+ $useParserCache = false;
+ }
+ );
+
+ $this->setTemporaryHook(
+ 'ArticleContentViewCustom',
+ function ( Content $content, Title $title, OutputPage $output ) use ( $page ) {
+ $this->assertSame( $page->getTitle(), $title, '$title' );
+ $this->assertSame( 'Test A', $content->getNativeData(), '$content' );
+
+ $output->addHTML( 'Hook Text' );
+ return false;
+ }
+ );
+
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
+ $this->assertContains( 'Hook Text', $this->getHtml( $output ) );
+ }
+
+ public function testArticleAfterFetchContentObjectHook() {
+ $page = $this->getPage( __METHOD__, [ 1 => 'Test A' ] );
+
+ $article = new Article( $page->getTitle(), 0 );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+
+ // use ArticleViewHeader hook to bypass the parser cache
+ $this->setTemporaryHook(
+ 'ArticleViewHeader',
+ function ( Article $articlePage, &$outputDone, &$useParserCache ) use ( $article ) {
+ $useParserCache = false;
+ }
+ );
+
+ $this->setTemporaryHook(
+ 'ArticleAfterFetchContentObject',
+ function ( Article &$articlePage, Content &$content ) use ( $page, $article ) {
+ $this->assertSame( $article, $articlePage, '$articlePage' );
+ $this->assertSame( 'Test A', $content->getNativeData(), '$content' );
+
+ $content = new WikitextContent( 'Hook Text' );
+ }
+ );
+
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertNotContains( 'Test A', $this->getHtml( $output ) );
+ $this->assertContains( 'Hook Text', $this->getHtml( $output ) );
+ }
+
+ public function testShowMissingArticleHook() {
+ $page = $this->getPage( __METHOD__ );
+
+ $article = new Article( $page->getTitle() );
+ $article->getContext()->getOutput()->setTitle( $page->getTitle() );
+
+ $this->setTemporaryHook(
+ 'ShowMissingArticle',
+ function ( Article $articlePage ) use ( $article ) {
+ $this->assertSame( $article, $articlePage, '$articlePage' );
+
+ $articlePage->getContext()->getOutput()->addHTML( 'Hook Text' );
+ }
+ );
+
+ $article->view();
+
+ $output = $article->getContext()->getOutput();
+ $this->assertContains( '(noarticletextanon)', $this->getHtml( $output ) );
+ $this->assertContains( 'Hook Text', $this->getHtml( $output ) );
+ }
+
+}
<?php
+use Wikimedia\TestingAccessWrapper;
+
/**
* @group ResourceLoader
*/
-class ResourceLoaderSkinModuleTest extends PHPUnit\Framework\TestCase {
+class ResourceLoaderSkinModuleTest extends MediaWikiTestCase {
use MediaWikiCoversValidator;
}
/**
- * @dataProvider provideGetLogo
- * @covers ResourceLoaderSkinModule::getLogo
+ * @dataProvider provideGetLogoData
+ * @covers ResourceLoaderSkinModule::getLogoData
*/
- public function testGetLogo( $config, $expected, $baseDir = null ) {
+ public function testGetLogoData( $config, $expected, $baseDir = null ) {
if ( $baseDir ) {
- $oldIP = $GLOBALS['IP'];
- $GLOBALS['IP'] = $baseDir;
- $teardown = new Wikimedia\ScopedCallback( function () use ( $oldIP ) {
- $GLOBALS['IP'] = $oldIP;
- } );
+ $this->setMwGlobals( 'IP', $baseDir );
}
+ // Allow testing of protected method
+ $module = TestingAccessWrapper::newFromObject( new ResourceLoaderSkinModule() );
$this->assertEquals(
$expected,
- ResourceLoaderSkinModule::getLogo( new HashConfig( $config ) )
+ $module->getLogoData( new HashConfig( $config ) )
);
}
- public function provideGetLogo() {
+ public function provideGetLogoData() {
return [
'simple' => [
'config' => [
],
];
}
+
+ /**
+ * @dataProvider providePreloadLinks
+ * @covers ResourceLoaderSkinModule::getPreloadLinks
+ * @covers ResourceLoaderSkinModule::getLogoPreloadlinks
+ * @covers ResourceLoaderSkinModule::getLogoData
+ */
+ public function testPreloadLinkHeaders( $config, $result ) {
+ $this->setMwGlobals( $config );
+ $ctx = $this->getMockBuilder( ResourceLoaderContext::class )
+ ->disableOriginalConstructor()->getMock();
+ $module = new ResourceLoaderSkinModule();
+
+ $this->assertEquals( [ $result ], $module->getHeaders( $ctx ) );
+ }
+
+ public function providePreloadLinks() {
+ return [
+ [
+ [
+ 'wgResourceBasePath' => '/w',
+ 'wgLogo' => '/img/default.png',
+ 'wgLogoHD' => [
+ '1.5x' => '/img/one-point-five.png',
+ '2x' => '/img/two-x.png',
+ ],
+ ],
+ 'Link: </img/default.png>;rel=preload;as=image;media=' .
+ 'not all and (min-resolution: 1.5dppx),' .
+ '</img/one-point-five.png>;rel=preload;as=image;media=' .
+ '(min-resolution: 1.5dppx) and (max-resolution: 1.999999dppx),' .
+ '</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
+ ],
+ [
+ [
+ 'wgResourceBasePath' => '/w',
+ 'wgLogo' => '/img/default.png',
+ 'wgLogoHD' => false,
+ ],
+ 'Link: </img/default.png>;rel=preload;as=image'
+ ],
+ [
+ [
+ 'wgResourceBasePath' => '/w',
+ 'wgLogo' => '/img/default.png',
+ 'wgLogoHD' => [
+ '2x' => '/img/two-x.png',
+ ],
+ ],
+ 'Link: </img/default.png>;rel=preload;as=image;media=' .
+ 'not all and (min-resolution: 2dppx),' .
+ '</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
+ ],
+ [
+ [
+ 'wgResourceBasePath' => '/w',
+ 'wgLogo' => '/img/default.png',
+ 'wgLogoHD' => [
+ 'svg' => '/img/vector.svg',
+ ],
+ ],
+ 'Link: </img/vector.svg>;rel=preload;as=image'
+
+ ],
+ [
+ [
+ 'wgResourceBasePath' => '/w',
+ 'wgLogo' => '/w/test.jpg',
+ 'wgLogoHD' => false,
+ 'wgUploadPath' => '/w/images',
+ 'IP' => dirname( dirname( __DIR__ ) ) . '/data/media',
+ ],
+ 'Link: </w/test.jpg?edcf2>;rel=preload;as=image',
+ ],
+ ];
+ }
}
result = mw.user.generateRandomSessionId();
assert.strictEqual( typeof result, 'string', 'type' );
assert.strictEqual( result.trim(), result, 'no whitespace at beginning or end' );
- assert.strictEqual( result.length, 16, 'size' );
+ assert.strictEqual( result.length, 20, 'size' );
result2 = mw.user.generateRandomSessionId();
assert.notEqual( result, result2, 'different when called multiple times' );
result = mw.user.generateRandomSessionId();
assert.strictEqual( typeof result, 'string', 'type' );
assert.strictEqual( result.trim(), result, 'no whitespace at beginning or end' );
- assert.strictEqual( result.length, 16, 'size' );
+ assert.strictEqual( result.length, 20, 'size' );
result2 = mw.user.generateRandomSessionId();
assert.notEqual( result, result2, 'different when called multiple times' );
var result = mw.user.getPageviewToken(),
result2 = mw.user.getPageviewToken();
assert.strictEqual( typeof result, 'string', 'type' );
- assert.strictEqual( /^[a-f0-9]{16}$/.test( result ), true, '16 HEX symbols string' );
+ assert.strictEqual( /^[a-f0-9]{20}$/.test( result ), true, '20 HEX symbols string' );
assert.strictEqual( result2, result, 'sticky' );
} );