/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>*/maintenance/7zip.inc</exclude-pattern>
<exclude-pattern>*/maintenance/CodeCleanerGlobalsPass.inc</exclude-pattern>
<exclude-pattern>*/maintenance/archives/upgradeLogging\.php</exclude-pattern>
- <exclude-pattern>*/maintenance/backup.inc</exclude-pattern>
<exclude-pattern>*/maintenance/benchmarks/bench_HTTP_HTTPS\.php</exclude-pattern>
<exclude-pattern>*/maintenance/benchmarks/bench_Wikimedia_base_convert\.php</exclude-pattern>
<exclude-pattern>*/maintenance/benchmarks/bench_delete_truncate\.php</exclude-pattern>
<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>
sitewide CSS/JS (and editing other users' CSS/JS). No other group has
'editsitecss', 'editusercss', 'editsitejs' or 'edituserjs' by default.
* A new grant group, 'editsiteconfig', is added for granting the above rights.
+* 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
additional links to the subtitle of a history page.
* The 'GetLinkColours' hook now receives an additional $title parameter,
the Title object of the page being parsed, on which the links will be shown.
+* (T194731) DifferenceEngine supports multiple slots. Added SlotDiffRenderer to
+ 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
Set $wgShowExceptionDetails and/or $wgShowHostnames instead.
* The $wgShowDBErrorBacktrace global is deprecated and nonfunctional.
Set $wgShowExceptionDetails instead.
-* Public access to the DifferenceEngine properties mOldid, mNewid, mOldPage,
- mNewPage, mOldContent, mNewContent, mRevisionsLoaded, mTextLoaded and
- mCacheHit is deprecated. Use getOldid() / getNewid() for the first two,
- do your own lookup for page/content. mNewRev / mOldRev remains public.
+* Public access to the DifferenceEngine properties mOldid, mNewid, mOldRev,
+ mNewRev, mOldPage, mNewPage, mOldContent, mNewContent, mRevisionsLoaded,
+ mTextLoaded and mCacheHit is deprecated. Use getOldid() / getNewid() /
+ getOldRevision() / getNewRevision() for the first four (note that the
+ revision ones return a RevisionRecord, not a Revision), do your own lookup
+ for page/content.
* The $wgExternalDiffEngine value 'wikidiff2' is deprecated. To use wikidiff2
just enable the PHP extension, and it will be autodetected.
+* (T194731) DifferenceEngine properties mOldContent and mNewContent and methods
+ setContent(), generateContentDiffBody(), generateTextDiffBody() and textDiff()
+ are deprecated. To interact with a single slot, use a SlotDiffRenderer (and
+ subclass it to customize diff rendering); to diff custom (e.g. unsaved)
+ content, use setRevisions(). Subclassing DifferenceEngine should only be done
+ to customize page-level diff properties (such as the navigation header).
* The wfUseMW function, soft-deprecated in 1.26, is now hard deprecated.
* All MagicWord static methods are now deprecated. Use the MagicWordFactory
methods instead.
* All SpecialPageFactory static methods are deprecated. Instead, call the
methods on a SpecialPageFactory instance, which may be obtained from
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.
+* (T197179) Parameters 'notice', 'notice-messages', 'notice-message',
+ previously used by OOUI HTMLForm fields, are now deprecated. Use
+ 'help', 'help-message', 'help-messages' instead.
+* (T197179) HTMLFormField::getNotices() is now deprecated.
+* The jquery.localize module is now deprecated. Use jquery.i18n 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() ) {
'ActiveUsersPager' => __DIR__ . '/includes/specials/pagers/ActiveUsersPager.php',
'ActivityUpdateJob' => __DIR__ . '/includes/jobqueue/jobs/ActivityUpdateJob.php',
'ActorMigration' => __DIR__ . '/includes/ActorMigration.php',
+ 'AddChangeTag' => __DIR__ . '/maintenance/addChangeTag.php',
'AddRFCandPMIDInterwiki' => __DIR__ . '/maintenance/addRFCandPMIDInterwiki.php',
'AddSite' => __DIR__ . '/maintenance/addSite.php',
'AjaxDispatcher' => __DIR__ . '/includes/AjaxDispatcher.php',
'AvroValidator' => __DIR__ . '/includes/utils/AvroValidator.php',
'BacklinkCache' => __DIR__ . '/includes/cache/BacklinkCache.php',
'BacklinkJobUtils' => __DIR__ . '/includes/jobqueue/utils/BacklinkJobUtils.php',
- 'BackupDumper' => __DIR__ . '/maintenance/backup.inc',
+ 'BackupDumper' => __DIR__ . '/maintenance/includes/BackupDumper.php',
'BackupReader' => __DIR__ . '/maintenance/importDump.php',
'BadRequestError' => __DIR__ . '/includes/exception/BadRequestError.php',
'BadTitleError' => __DIR__ . '/includes/exception/BadTitleError.php',
'BcryptPassword' => __DIR__ . '/includes/password/BcryptPassword.php',
'BenchHttpHttps' => __DIR__ . '/maintenance/benchmarks/bench_HTTP_HTTPS.php',
'BenchIfSwitch' => __DIR__ . '/maintenance/benchmarks/bench_if_switch.php',
- 'BenchStrtrStrReplace' => __DIR__ . '/maintenance/benchmarks/bench_strtr_str_replace.php',
'BenchUtf8TitleCheck' => __DIR__ . '/maintenance/benchmarks/bench_utf8_title_check.php',
'BenchWfIsWindows' => __DIR__ . '/maintenance/benchmarks/bench_wfIsWindows.php',
'BenchWikimediaBaseConvert' => __DIR__ . '/maintenance/benchmarks/bench_Wikimedia_base_convert.php',
'BenchmarkParse' => __DIR__ . '/maintenance/benchmarks/benchmarkParse.php',
'BenchmarkPurge' => __DIR__ . '/maintenance/benchmarks/benchmarkPurge.php',
'BenchmarkSanitizer' => __DIR__ . '/maintenance/benchmarks/benchmarkSanitizer.php',
+ 'BenchmarkStringReplacement' => __DIR__ . '/maintenance/benchmarks/benchmarkStringReplacement.php',
'BenchmarkTidy' => __DIR__ . '/maintenance/benchmarks/benchmarkTidy.php',
'BenchmarkTitleValue' => __DIR__ . '/maintenance/benchmarks/benchmarkTitleValue.php',
'Benchmarker' => __DIR__ . '/maintenance/benchmarks/Benchmarker.php',
'DiffOpCopy' => __DIR__ . '/includes/diff/DairikiDiff.php',
'DiffOpDelete' => __DIR__ . '/includes/diff/DairikiDiff.php',
'DifferenceEngine' => __DIR__ . '/includes/diff/DifferenceEngine.php',
+ 'DifferenceEngineSlotDiffRenderer' => __DIR__ . '/includes/diff/DifferenceEngineSlotDiffRenderer.php',
'Digit2Html' => __DIR__ . '/maintenance/language/digit2html.php',
'DjVuHandler' => __DIR__ . '/includes/media/DjVuHandler.php',
'DjVuImage' => __DIR__ . '/includes/media/DjVuImage.php',
'Maintenance' => __DIR__ . '/maintenance/Maintenance.php',
'MakeTestEdits' => __DIR__ . '/maintenance/makeTestEdits.php',
'MalformedTitleException' => __DIR__ . '/includes/title/MalformedTitleException.php',
+ 'ManageForeignResources' => __DIR__ . '/maintenance/resources/manageForeignResources.php',
'ManageJobs' => __DIR__ . '/maintenance/manageJobs.php',
'ManualLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
'MapCacheLRU' => __DIR__ . '/includes/libs/MapCacheLRU.php',
'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',
'SkinFallbackTemplate' => __DIR__ . '/includes/skins/SkinFallbackTemplate.php',
'SkinTemplate' => __DIR__ . '/includes/skins/SkinTemplate.php',
'SlideshowImageGallery' => __DIR__ . '/includes/gallery/SlideshowImageGallery.php',
+ 'SlotDiffRenderer' => __DIR__ . '/includes/diff/SlotDiffRenderer.php',
'SpecialActiveUsers' => __DIR__ . '/includes/specials/SpecialActiveusers.php',
'SpecialAllMessages' => __DIR__ . '/includes/specials/SpecialAllMessages.php',
'SpecialAllMyUploads' => __DIR__ . '/includes/specials/SpecialMyRedirectPages.php',
'TextContent' => __DIR__ . '/includes/content/TextContent.php',
'TextContentHandler' => __DIR__ . '/includes/content/TextContentHandler.php',
'TextPassDumper' => __DIR__ . '/maintenance/dumpTextPass.php',
+ 'TextSlotDiffRenderer' => __DIR__ . '/includes/diff/TextSlotDiffRenderer.php',
'TextStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
'TgConverter' => __DIR__ . '/languages/classes/LanguageTg.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",
&$sortkey: Sortkey to use.
'GetDifferenceEngine': Called when getting a new difference engine interface
-object Return false for valid object in $differenceEngine or true for the
-default difference engine.
+object. Can be used to decorate or replace the default difference engine.
$context: IContextSource context to be used for diff
$old: Revision ID to show and diff with
$new: Either a revision ID or one of the strings 'cur', 'prev' or 'next'
$refreshCache: If set, refreshes the diff cache
$unhide: If set, allow viewing deleted revs
-&$differenceEngine: output parameter, difference engine object to be used for
- diff
+&$differenceEngine: The difference engine object to be used for the diff
'GetDoubleUnderscoreIDs': Modify the list of behavior switch (double
underscore) magic words. Called by MagicWord.
$user: User whose preferences are being used to make timestamp
$lang: Language that will be used to render the timestamp
+'GetSlotDiffRenderer': Replace or wrap the standard SlotDiffRenderer for some
+content type.
+$contentHandler: ContentHandler for which the slot diff renderer is fetched.
+&$slotDiffRenderer: SlotDiffRenderer to change or replace.
+$context: IContextSource
+
'getUserPermissionsErrors': Add a permissions error when permissions errors are
checked for. Use instead of userCan for most cases. Return false if the user
can't do it, and populate $result with the reason in the form of
*/
$wgPasswordSender = false;
-/**
- * Sender name for e-mail notifications.
- *
- * @deprecated since 1.23; use the system message 'emailsender' instead.
- */
-$wgPasswordSenderName = 'MediaWiki Mail';
-
/**
* Reply-To address for e-mail notifications.
*
$wgResourceLoaderValidateJS = true;
/**
- * If set to true, statically-sourced (file-backed) JavaScript resources will
- * be parsed for validity before being bundled up into ResourceLoader modules.
+ * When enabled, execution of JavaScript modules is profiled client-side.
*
- * This can be helpful for development by providing better error messages in
- * default (non-debug) mode, but JavaScript parsing is slow and memory hungry
- * and may fail on large pre-bundled frameworks.
+ * Instrumentation happens in mw.loader.profiler.
+ * Use `mw.inspect('time')` from the browser console to display the data.
+ *
+ * @since 1.32
*/
-$wgResourceLoaderValidateStaticJS = false;
+$wgResourceLoaderEnableJSProfiler = false;
/**
* Whether ResourceLoader should attempt to persist modules in localStorage on
$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 );
}
/**
- * Output a "<script>" tag with the given contents.
+ * Output an HTML script tag with the given contents.
*
- * @todo do some useful escaping as well, like if $contents contains
- * literal "</script>" or (for XML) literal "]]>".
+ * It is unsupported for the contents to contain the sequence `<script` or `</script`
+ * (case-insensitive). This ensures the script can be terminated easily and consistently.
+ * It is the responsibility of the caller to avoid such character sequence by escaping
+ * or avoiding it. If found at run-time, the contents are replaced with a comment, and
+ * a warning is logged server-side.
*
* @param string $contents JavaScript
* @param string|null $nonce Nonce for CSP header, from OutputPage::getCSPNonce()
}
}
- if ( preg_match( '/[<&]/', $contents ) ) {
- $contents = "/*<![CDATA[*/$contents/*]]>*/";
+ if ( preg_match( '/<\/?script/i', $contents ) ) {
+ wfLogWarning( __METHOD__ . ': Illegal character sequence found in inline script.' );
+ $contents = '/* ERROR: Invalid script */';
}
return self::rawElement( 'script', $attrs, $contents );
$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();
foreach ( $this->contentOverrideCallbacks as $callback ) {
$content = $callback( $title );
if ( $content !== null ) {
+ $text = ContentHandler::getContentText( $content );
+ if ( strpos( $text, '</script>' ) !== false ) {
+ // Proactively replace this so that we can display a message
+ // to the user, instead of letting it go to Html::inlineScript(),
+ // where it would be considered a server-side issue.
+ $titleFormatted = $title->getPrefixedText();
+ $content = new JavaScriptContent(
+ Xml::encodeJsCall( 'mw.log.error', [
+ "Cannot preview $titleFormatted due to script-closing tag."
+ ] )
+ );
+ }
return $content;
}
}
] );
}
- /**
- * 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.
*
'BlobStoreFactory' => function ( MediaWikiServices $services ) : BlobStoreFactory {
return new BlobStoreFactory(
- $services->getDBLoadBalancer(),
+ $services->getDBLoadBalancerFactory(),
$services->getMainWANObjectCache(),
$services->getMainConfig(),
$services->getContentLanguage()
* 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 {
use Config;
use Language;
use WANObjectCache;
-use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\LBFactory;
/**
* Service for instantiating BlobStores
class BlobStoreFactory {
/**
- * @var LoadBalancer
+ * @var LBFactory
*/
- private $loadBalancer;
+ private $lbFactory;
/**
* @var WANObjectCache
private $contLang;
public function __construct(
- LoadBalancer $loadBalancer,
+ LBFactory $lbFactory,
WANObjectCache $cache,
Config $mainConfig,
Language $contLang
) {
- $this->loadBalancer = $loadBalancer;
+ $this->lbFactory = $lbFactory;
$this->cache = $cache;
$this->config = $mainConfig;
$this->contLang = $contLang;
* @return SqlBlobStore
*/
public function newSqlBlobStore( $wikiId = false ) {
+ $lb = $this->lbFactory->getMainLB( $wikiId );
$store = new SqlBlobStore(
- $this->loadBalancer,
+ $lb,
$this->cache,
$wikiId
);
$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 );
};
// NOTE: even when this class is set to not read from the old schema, callers
// should still be able to join against the text table, as long as we are still
// writing the old schema for compatibility.
- wfDeprecated( __METHOD__ . ' with `text` option', '1.32' );
+ // TODO: This should trigger a deprecation warning eventually (T200918), but not
+ // before all known usages are removed (see T198341 and T201164).
+ // wfDeprecated( __METHOD__ . ' with `text` option', '1.32' );
}
$ret['tables'][] = 'text';
*
* @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';
+ }
+
}
}
/** @var bool Boolean for initialisation on demand */
public $mRestrictionsLoaded = false;
- /** @var string Text form including namespace/interwiki, initialised on demand */
- protected $mPrefixedText = null;
+ /**
+ * Text form including namespace/interwiki, initialised on demand
+ *
+ * Only public to share cache with TitleFormatter
+ *
+ * @private
+ * @var string
+ */
+ public $prefixedText = null;
/** @var mixed Cached value for getTitleProtection (create protection) */
public $mTitleProtection;
);
}
+ /**
+ * 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?
*
* @return string The prefixed title, with spaces
*/
public function getPrefixedText() {
- if ( $this->mPrefixedText === null ) {
+ if ( $this->prefixedText === null ) {
$s = $this->prefix( $this->mTextform );
$s = strtr( $s, '_', ' ' );
- $this->mPrefixedText = $s;
+ $this->prefixedText = $s;
}
- return $this->mPrefixedText;
+ return $this->prefixedText;
}
/**
$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();
$result_array['text'] = $p_result->getText( [
'allowTOC' => !$params['disabletoc'],
'enableSectionEditLinks' => !$params['disableeditsection'],
- 'unwrap' => $params['wrapoutputclass'] === '',
+ 'wrapperDivClass' => $params['wrapoutputclass'],
'deduplicateStyles' => !$params['disablestylededuplication'],
] );
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
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'
return self::ERROR_CACHE;
}
} else {
+ // @todo Doesn't seem reachable, see @todo in buildStashValue
$logger->info( "Uncacheable parser output for key '{cachekey}' ('{title}') [{code}].",
[ 'cachekey' => $key, 'title' => $titleStr, 'code' => $code ] );
return self::ERROR_UNCACHEABLE;
}
if ( $ttl <= 0 ) {
+ // @todo It doesn't seem like this can occur, because it would mean an entry older than
+ // getCacheExpiry() seconds, which is much longer than PRESUME_FRESH_TTL_SEC, and
+ // anything older than PRESUME_FRESH_TTL_SEC will have been thrown out already.
return [ null, 0, 'no_ttl' ];
}
"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 різниці версій, у байтах.",
"Umherirrender",
"NeverBehave",
"Wbxshiori",
- "Wxyveronica"
+ "Wxyveronica",
+ "WhitePhosphorus"
]
},
"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/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n<strong>错误请求:</strong>当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:Special:MyLanguage/API:Errors_and_warnings|API:错误与警告]]。\n\n<p class=\"mw-apisandbox-link\"><strong>测试中:</strong>测试API请求的易用性,请参见[[Special:ApiSandbox]]。</p>",
"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": "从哪个修订版本时间戳开始列举。",
"apierror-mustbeloggedin-linkaccounts": "您必须登录以链接账户。",
"apierror-mustbeloggedin-removeauth": "您必须登录以移除身份验证数据。",
"apierror-mustbeloggedin-uploadstash": "上传暂存功能只对已登录用户可用。",
- "apierror-mustbeloggedin": "您必须登录至$1。",
+ "apierror-mustbeloggedin": "您必须登录才能$1。",
"apierror-mustbeposted": "<kbd>$1</kbd>模块需要POST请求。",
"apierror-mustpostparams": "以下{{PLURAL:$2|参数}}在查询字符串中被找到,但必须在POST正文中:$1。",
"apierror-noapiwrite": "通过API编辑此wiki已禁用。请确保<code>$wgEnableWriteAPI=true;</code>声明包含在wiki的<code>LocalSettings.php</code>文件中。",
"Wwycheuk",
"Wbxshiori",
"Sanmosa",
- "Kly"
+ "Kly",
+ "WhitePhosphorus"
]
},
"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/ mediawiki-api-announce 郵寄清單]以便獲得更新通知。\n\n<strong>錯誤的請求:</strong>當 API 收到錯誤的請求,會發出以「MediaWiki-API-Error」為鍵的 HTTP 標頭欄位,隨後標頭欄位的值,以及傳回的錯誤碼會設為相同值。詳細資訊請參閱 [[mw:Special:MyLanguage/API:Errors_and_warnings|API: 錯誤與警告]]。\n\n<p class=\"mw-apisandbox-link\"><strong>測試:</strong>要簡化 API 請求的測試過程,請見 [[Special:ApiSandbox]]。</p>",
"apihelp-block-param-allowusertalk": "允許使用者編輯自己的對話頁面 (依據 <var>[[mw:Special:MyLanguage/Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var> 的設定)。",
"apihelp-block-param-reblock": "若使用者已被封鎖,覆寫既有的封鎖設定值。",
"apihelp-block-param-watchuser": "監視使用者或 IP 位址的使用者頁面與對話頁面。",
+ "apihelp-block-param-tags": "在封鎖日誌裡更改套用到項目的標籤。",
"apihelp-block-example-ip-simple": "封鎖 IP 位址 <kbd>192.0.2.5</kbd> 三天,原因為 <kbd>First strike</kbd>。",
"apihelp-block-example-user-complex": "永久封鎖 IP 位址 <kbd>Vandal</kbd>,原因為 <kbd>Vandalism</kbd>。",
"apihelp-changeauthenticationdata-summary": "為目前使用者變更身分核對資料。",
+ "apihelp-changeauthenticationdata-example-password": "嘗試更改目前使用者的密碼至 <kbd>ExamplePassword</kbd>。",
"apihelp-checktoken-summary": "檢查來自 <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> 的密鑰有效性。",
"apihelp-checktoken-param-type": "要測試的密鑰類型。",
"apihelp-checktoken-param-token": "要測試的密鑰。",
"apihelp-feedrecentchanges-param-feedformat": "摘要格式。",
"apihelp-feedrecentchanges-param-namespace": "用於限制結果的命名空間。",
"apihelp-feedrecentchanges-param-invert": "除所選定者外的所有命名空間。",
+ "apihelp-feedrecentchanges-param-days": "用於限制結果的天數。",
"apihelp-feedrecentchanges-param-limit": "回傳的結果數量上限。",
+ "apihelp-feedrecentchanges-param-from": "顯示自那時以來的更改。",
"apihelp-feedrecentchanges-param-hideminor": "隱藏小編輯。",
"apihelp-feedrecentchanges-param-hidebots": "隱藏由機器人做的變更。",
"apihelp-feedrecentchanges-param-hideanons": "隱藏匿名使用者做的變更。",
"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-login-example-login": "登入",
"apihelp-logout-summary": "登出並清除 session 資料。",
"apihelp-logout-example-logout": "登出當前使用者",
+ "apihelp-managetags-summary": "執行相關到更改標籤的管理任務。",
"apihelp-managetags-param-tags": "在標籤管理日誌裡更改套用到項目的標籤。",
"apihelp-mergehistory-summary": "合併頁面歷史",
"apihelp-mergehistory-param-reason": "合併歷史的原因。",
"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-paraminfo-param-helpformat": "說明字串的格式。",
"apihelp-parse-param-summary": "解析摘要。",
"apihelp-parse-param-pageid": "解析此頁面的內容。覆蓋 <var>$1page</var>。",
+ "apihelp-parse-param-redirects": "若 <var>$1page</var> 或者 <var>$1pageid</var> 被設定成重新導向,則解析它。",
"apihelp-parse-param-prop": "要取得的資訊部份:",
"apihelp-parse-paramvalue-prop-headhtml": "取得頁面已解析的 <code><head></code>。",
"apihelp-parse-param-disablepp": "請改用<var>$1disablelimitreport</var>。",
"apihelp-purge-param-forcelinkupdate": "更新連結表格。",
"apihelp-purge-example-generator": "重新整理主要命名空間的前10個頁面。",
"apihelp-query-summary": "擷取來自及有關MediaWiki的數據。",
+ "apihelp-query-param-prop": "替已查詢頁面所要取得的屬性。",
"apihelp-query-param-list": "要取得的清單。",
"apihelp-query-param-meta": "要取得的詮釋資料。",
"apihelp-query+allcategories-summary": "列舉所有分類。",
"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+allfileusages-param-dir": "列出時所採用的方向。",
"apihelp-query+allfileusages-example-unique": "列出唯一的檔案標題。",
"apihelp-query+allfileusages-example-unique-generator": "取得所有檔案標題,標記為遺失。",
"apihelp-query+allfileusages-example-generator": "取得包含檔案的頁面。",
+ "apihelp-query+allimages-summary": "按順序列舉所有圖片。",
"apihelp-query+allimages-param-sort": "作為排序順序的屬性。",
"apihelp-query+allimages-param-dir": "列出時所採用的方向。",
"apihelp-query+allimages-param-minsize": "限制圖片至少要有這樣多的位元組。",
"apihelp-query+allimages-param-maxsize": "限制圖片最多只能這樣多的位元組。",
+ "apihelp-query+allimages-param-sha1": "圖片的 SHA1 雜湊值。覆蓋 $1sha1base36。",
+ "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-paramvalue-prop-title": "添加連結標題。",
"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+allmessages-param-prop": "要取得的屬性。",
"apihelp-query+allmessages-param-lang": "以此語言來回傳訊息。",
"apihelp-query+allmessages-param-from": "以此訊息來回傳訊息開頭。",
"apihelp-query+allmessages-param-to": "以此訊息來回傳訊息結尾。",
+ "apihelp-query+allmessages-param-prefix": "回傳帶有前綴的訊息。",
+ "apihelp-query+allmessages-example-ipb": "顯示以 <kbd>ipb-</kbd> 起始的訊息。",
+ "apihelp-query+allmessages-example-de": "顯示在德語裡的 <kbd>august</kbd> 與 <kbd>mainpage</kbd> 訊息。",
"apihelp-query+allpages-param-from": "起始列舉的頁面標題。",
"apihelp-query+allpages-param-to": "終止列舉的頁面標題。",
"apihelp-query+allpages-param-prefix": "搜尋以此值為開頭的所有頁面標題。",
"apihelp-query+allredirects-param-namespace": "要列舉的命名空間。",
"apihelp-query+allredirects-param-limit": "要回傳的項目總數。",
"apihelp-query+allredirects-param-dir": "列出時所採用的方向。",
+ "apihelp-query+allredirects-example-unique-generator": "取得所有目標頁面,標記為遺失。",
"apihelp-query+allredirects-example-generator": "取得包含重新導向的頁面。",
"apihelp-query+allrevisions-summary": "列出所有修訂版本。",
"apihelp-query+allrevisions-param-start": "起始列舉的時間戳記。",
"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+backlinks-param-namespace": "要列舉的命名空間。",
"apihelp-query+backlinks-param-dir": "列出時所採用的方向。",
+ "apihelp-query+backlinks-example-simple": "顯示至 <kbd>Main page</kbd> 的連結。",
"apihelp-query+blocks-summary": "列出所有被封鎖使用者與 IP 位址。",
"apihelp-query+blocks-param-start": "起始列舉的時間戳記。",
"apihelp-query+blocks-param-end": "終止列舉的時間戳記。",
"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+categorymembers-param-endsortkey": "請改用 $1endhexsortkey。",
"apihelp-query+categorymembers-example-simple": "取得在 <kbd>Category:Physics</kbd> 裡前 10 項的頁面。",
"apihelp-query+contributors-param-limit": "要回傳的貢獻人員數量。",
"apihelp-query+deletedrevisions-summary": "取得已刪除修訂的資訊。",
+ "apihelp-query+deletedrevisions-param-tag": "僅列出以此標籤所標記的修訂。",
"apihelp-query+deletedrevisions-param-user": "此列出由該使用者作出的修訂。",
"apihelp-query+deletedrevisions-param-excludeuser": "不要列出由該使用者作出的修訂。",
"apihelp-query+deletedrevs-summary": "列出已刪除的修訂。",
"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+embeddedin-param-limit": "要回傳的頁面總數。",
"apihelp-query+extlinks-summary": "回傳所有指定頁面的外部 URL (非 interwiki)。",
"apihelp-query+extlinks-param-limit": "要回傳的連結數量。",
+ "apihelp-query+exturlusage-param-prop": "要包含的資訊部份:",
"apihelp-query+exturlusage-paramvalue-prop-ids": "添加頁面 ID。",
+ "apihelp-query+exturlusage-paramvalue-prop-title": "添加標題與頁面的命名空間 ID。",
"apihelp-query+exturlusage-paramvalue-prop-url": "添加用於頁面的 URL。",
"apihelp-query+exturlusage-param-namespace": "要列舉的頁面命名空間。",
"apihelp-query+exturlusage-param-limit": "要回傳的頁面數量。",
+ "apihelp-query+filearchive-param-from": "起始列舉的圖片標題。",
+ "apihelp-query+filearchive-param-to": "終止列舉的圖片標題。",
"apihelp-query+filearchive-param-limit": "要回傳的圖片總數。",
"apihelp-query+filearchive-param-dir": "列出時所採用的方向。",
"apihelp-query+filearchive-param-sha1": "圖片的 SHA1 雜湊值。覆蓋 $1sha1base36。",
"apihelp-query+filearchive-param-prop": "要取得的圖片資訊:",
"apihelp-query+filearchive-paramvalue-prop-sha1": "替圖片添加 SHA-1 雜湊值。",
+ "apihelp-query+filearchive-paramvalue-prop-description": "添加圖片版本的描述。",
+ "apihelp-query+filearchive-paramvalue-prop-parseddescription": "解析版本的描述。",
"apihelp-query+filearchive-paramvalue-prop-mime": "添加圖片的 MIME。",
"apihelp-query+filearchive-paramvalue-prop-mediatype": "添加圖片的媒體類型。",
+ "apihelp-query+filearchive-paramvalue-prop-metadata": "列出圖片版本的 Exif 詮釋資料。",
+ "apihelp-query+filearchive-paramvalue-prop-bitdepth": "添加版本的位元深度。",
+ "apihelp-query+filearchive-example-simple": "顯示所有已刪除檔案的清單。",
+ "apihelp-query+filerepoinfo-paramvalue-prop-url": "公共區域 URL 路徑。",
"apihelp-query+fileusage-param-prop": "要取得的屬性。",
"apihelp-query+fileusage-paramvalue-prop-pageid": "各頁面的頁面 ID。",
"apihelp-query+fileusage-paramvalue-prop-title": "各頁面的標題。",
"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-paramvalue-prop-redirect": "若頁面為重新導向,則做出標記。",
+ "apihelp-query+linkshere-param-namespace": "僅包含這些命名空間的頁面。",
"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+prefixsearch-param-limit": "回傳的結果數量上限。",
"apihelp-query+prefixsearch-param-offset": "要略過的結果數量。",
+ "apihelp-query+protectedtitles-param-namespace": "僅列出這些命名空間的標題。",
+ "apihelp-query+protectedtitles-param-level": "僅列出具有這些保護層級的標題。",
"apihelp-query+protectedtitles-param-limit": "要回傳的頁面總數。",
"apihelp-query+protectedtitles-param-prop": "要取得的屬性。",
+ "apihelp-query+protectedtitles-paramvalue-prop-level": "添加保護層級。",
"apihelp-query+protectedtitles-example-simple": "列出已保護的標題。",
"apihelp-query+querypage-param-limit": "回傳的結果數量。",
"apihelp-query+random-summary": "取得隨機頁面集合",
"apihelp-query+redirects-param-prop": "要取得的屬性。",
"apihelp-query+redirects-paramvalue-prop-pageid": "各重新導向的頁面 ID。",
"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+userinfo-paramvalue-prop-email": "添加使用者的電子郵件地址與電子郵件驗證日期。",
"apihelp-query+userinfo-paramvalue-prop-registrationdate": "添加使用者的註冊日期。",
"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;
}
}
$taglessDesc = Sanitizer::stripAllTags( $originalDesc->parse() );
- $escapedDesc = Sanitizer::escapeHtmlAllowEntities( $taglessDesc );
- return $context->getLanguage()->truncateForVisual( $escapedDesc, $length );
+ return $context->getLanguage()->truncateForVisual( $taglessDesc, $length );
}
/**
}
// insert a row into change_tag for each new tag
+ $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
if ( count( $tagsToAdd ) ) {
$changeTagMapping = [];
if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD ) {
- $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
-
foreach ( $tagsToAdd as $tag ) {
$changeTagMapping[$tag] = $changeTagDefStore->acquireId( $tag );
}
$tagsRows = [];
foreach ( $tagsToAdd as $tag ) {
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $tagName = null;
+ } else {
+ $tagName = $tag;
+ }
// Filter so we don't insert NULLs as zero accidentally.
// Keep in mind that $rc_id === null means "I don't care/know about the
// rc_id, just delete $tag on this revision/log entry". It doesn't
// mean "only delete tags on this revision/log WHERE rc_id IS NULL".
$tagsRows[] = array_filter(
[
- 'ct_tag' => $tag,
+ 'ct_tag' => $tagName,
'ct_rc_id' => $rc_id,
'ct_log_id' => $log_id,
'ct_rev_id' => $rev_id,
// delete from change_tag
if ( count( $tagsToRemove ) ) {
foreach ( $tagsToRemove as $tag ) {
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $tagName = null;
+ $tagId = $changeTagDefStore->getId( $tag );
+ } else {
+ $tagName = $tag;
+ $tagId = null;
+ }
$conds = array_filter(
[
- 'ct_tag' => $tag,
+ 'ct_tag' => $tagName,
'ct_rc_id' => $rc_id,
'ct_log_id' => $log_id,
- 'ct_rev_id' => $rev_id
+ 'ct_rev_id' => $rev_id,
+ 'ct_tag_id' => $tagId,
]
);
$dbw->delete( 'change_tag', $conds, __METHOD__ );
public static function modifyDisplayQuery( &$tables, &$fields, &$conds,
&$join_conds, &$options, $filter_tag = ''
) {
- global $wgUseTagFilter;
+ global $wgUseTagFilter, $wgChangeTagsSchemaMigrationStage;
// Normalize to arrays
$tables = (array)$tables;
throw new MWException( 'Unable to determine appropriate JOIN condition for tagging.' );
}
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $tables[] = 'change_tag_def';
+ $join_cond = [ $join_cond, 'ct_tag_id=ctd_id' ];
+ $field = 'ctd_name';
+ } else {
+ $field = 'ct_tag';
+ }
+
$fields['ts_tags'] = wfGetDB( DB_REPLICA )->buildGroupConcatField(
- ',', 'change_tag', 'ct_tag', $join_cond
+ ',', 'change_tag', $field, $join_cond
);
if ( $wgUseTagFilter && $filter_tag ) {
$tables[] = 'change_tag';
$join_conds['change_tag'] = [ 'INNER JOIN', $join_cond ];
- $conds['ct_tag'] = $filter_tag;
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $tables[] = 'change_tag_def';
+ $join_conds['change_tag_def'] = [ 'INNER JOIN', 'ct_tag_id=ctd_id' ];
+ $conds['ctd_name'] = $filter_tag;
+ } else {
+ $conds['ct_tag'] = $filter_tag;
+ }
+
if (
is_array( $filter_tag ) && count( $filter_tag ) > 1 &&
!in_array( 'DISTINCT', $options )
* Extensions should NOT use this function; they can use the ListDefinedTags
* hook instead.
*
- * Includes a call to ChangeTag::canDeleteTag(), so your code doesn't need to
+ * Includes a call to ChangeTag::canCreateTag(), so your code doesn't need to
* do that.
*
* @param string $tag
// delete from valid_tag and/or set ctd_user_defined = 0
self::undefineTag( $tag );
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $tagId = MediaWikiServices::getInstance()->getChangeTagDefStore()->getId( $tag );
+ $conditions = [ 'ct_tag_id' => $tagId ];
+ } else {
+ $conditions = [ 'ct_tag' => $tag ];
+ }
+
// find out which revisions use this tag, so we can delete from tag_summary
$result = $dbw->select( 'change_tag',
- [ 'ct_rc_id', 'ct_log_id', 'ct_rev_id', 'ct_tag' ],
- [ 'ct_tag' => $tag ],
+ [ 'ct_rc_id', 'ct_log_id', 'ct_rev_id' ],
+ $conditions,
__METHOD__ );
foreach ( $result as $row ) {
// remove the tag from the relevant row of tag_summary
}
// delete from change_tag
- $dbw->delete( 'change_tag', [ 'ct_tag' => $tag ], __METHOD__ );
+ if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+ $tagId = MediaWikiServices::getInstance()->getChangeTagDefStore()->getId( $tag );
+ $dbw->delete( 'change_tag', [ 'ct_tag_id' => $tagId ], __METHOD__ );
+ } else {
+ $dbw->delete( 'change_tag', [ 'ct_tag' => $tag ], __METHOD__ );
+ }
if ( $wgChangeTagsSchemaMigrationStage > MIGRATION_OLD ) {
$dbw->delete( 'change_tag_def', [ 'ctd_name' => $tag ], __METHOD__ );
<?php
-
-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
/**
* Factory for creating an appropriate DifferenceEngine for this content model.
+ * Since 1.32, this is only used for page-level diffs; to diff two content objects,
+ * use getSlotDiffRenderer.
+ *
+ * The DifferenceEngine subclass to use is selected in getDiffEngineClass(). The
+ * GetDifferenceEngine hook will receive the DifferenceEngine object and can replace or
+ * wrap it.
+ * (Note that in older versions of MediaWiki the hook documentation instructed extensions
+ * to return false from the hook; you should not rely on always being able to decorate
+ * the DifferenceEngine instance from the hook. If the owner of the content type wants to
+ * decorare the instance, overriding this method is a safer approach.)
+ *
+ * @todo This is page-level functionality so it should not belong to ContentHandler.
+ * Move it to a better place once one exists (e.g. PageTypeHandler).
*
* @since 1.21
*
$rcid = 0, // FIXME: Deprecated, no longer used
$refreshCache = false, $unhide = false
) {
- // hook: get difference engine
- $differenceEngine = null;
- if ( !Hooks::run( 'GetDifferenceEngine',
- [ $context, $old, $new, $refreshCache, $unhide, &$differenceEngine ]
- ) ) {
- return $differenceEngine;
- }
$diffEngineClass = $this->getDiffEngineClass();
- return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
+ $differenceEngine = new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
+ Hooks::run( 'GetDifferenceEngine', [ $context, $old, $new, $refreshCache, $unhide,
+ &$differenceEngine ] );
+ return $differenceEngine;
+ }
+
+ /**
+ * Get an appropriate SlotDiffRenderer for this content model.
+ * @since 1.32
+ * @param IContextSource $context
+ * @return SlotDiffRenderer
+ */
+ final public function getSlotDiffRenderer( IContextSource $context ) {
+ $slotDiffRenderer = $this->getSlotDiffRendererInternal( $context );
+ if ( get_class( $slotDiffRenderer ) === TextSlotDiffRenderer::class ) {
+ // To keep B/C, when SlotDiffRenderer is not overridden for a given content type
+ // but DifferenceEngine is, use that instead.
+ $differenceEngine = $this->createDifferenceEngine( $context );
+ if ( get_class( $differenceEngine ) !== DifferenceEngine::class ) {
+ // TODO turn this into a deprecation warning in a later release
+ LoggerFactory::getInstance( 'diff' )->info(
+ 'Falling back to DifferenceEngineSlotDiffRenderer', [
+ 'modelID' => $this->getModelID(),
+ 'DifferenceEngine' => get_class( $differenceEngine ),
+ ] );
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ }
+ }
+ Hooks::run( 'GetSlotDiffRenderer', [ $this, &$slotDiffRenderer, $context ] );
+ return $slotDiffRenderer;
+ }
+
+ /**
+ * Return the SlotDiffRenderer appropriate for this content handler.
+ * @param IContextSource $context
+ * @return SlotDiffRenderer
+ */
+ protected function getSlotDiffRendererInternal( IContextSource $context ) {
+ $contentLanguage = MediaWikiServices::getInstance()->getContentLanguage();
+ $statsdDataFactory = MediaWikiServices::getInstance()->getStatsdDataFactory();
+ $slotDiffRenderer = new TextSlotDiffRenderer();
+ $slotDiffRenderer->setStatsdDataFactory( $statsdDataFactory );
+ // XXX using the page language would be better, but it's unclear how that should be injected
+ $slotDiffRenderer->setLanguage( $contentLanguage );
+ $slotDiffRenderer->setWikiDiff2MovedParagraphDetectionCutoff(
+ $context->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' )
+ );
+
+ $engine = DifferenceEngine::getEngine();
+ if ( $engine === false ) {
+ $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_PHP );
+ } elseif ( $engine === 'wikidiff2' ) {
+ $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_WIKIDIFF2 );
+ } else {
+ $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_EXTERNAL, $engine );
+ }
+
+ return $slotDiffRenderer;
}
/**
* 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
$html = '';
}
+ $output->clearWrapperDivClass();
$output->setText( $html );
}
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;
return false;
}
- /**
- * Usually aborts on failure
- * @param string $server
- * @param string $user
- * @param string $password
- * @param string $dbName
- * @throws DBConnectionError
- * @return resource|null
- */
function open( $server, $user, $password, $dbName ) {
global $wgDBOracleDRCP;
if ( !function_exists( 'oci_connect' ) ) {
$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
$this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' );
- return $this->conn;
+ return (bool)$this->conn;
}
/**
// Make sure all links update threads see the changes of each other.
// This handles the case when updates have to batched into several COMMITs.
$scopedLock = LinksUpdate::acquirePageLock( $this->getDB(), $id );
+ if ( !$scopedLock ) {
+ throw new RuntimeException( "Could not acquire lock for page ID '{$id}'." );
+ }
}
$title = $this->page->getTitle();
*/
use Wikimedia\Rdbms\IDatabase;
+use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use Wikimedia\ScopedCallback;
// Make sure all links update threads see the changes of each other.
// This handles the case when updates have to batched into several COMMITs.
$scopedLock = self::acquirePageLock( $this->getDB(), $this->mId );
+ if ( !$scopedLock ) {
+ throw new RuntimeException( "Could not acquire lock for page ID '{$this->mId}'." );
+ }
}
// Avoid PHP 7.1 warning from passing $this by reference
* @param IDatabase $dbw
* @param int $pageId
* @param string $why One of (job, atomicity)
- * @return ScopedCallback
- * @throws RuntimeException
+ * @return ScopedCallback|null
* @since 1.27
*/
public static function acquirePageLock( IDatabase $dbw, $pageId, $why = 'atomicity' ) {
$key = "LinksUpdate:$why:pageid:$pageId";
$scopedLock = $dbw->getScopedLockAndFlush( $key, __METHOD__, 15 );
if ( !$scopedLock ) {
- throw new RuntimeException( "Could not acquire lock '$key'." );
+ $logger = LoggerFactory::getInstance( 'SecondaryDataUpdate' );
+ $logger->info( "Could not acquire lock '{key}' for page ID '{page_id}'.", [
+ 'key' => $key,
+ 'page_id' => $pageId,
+ ] );
+ return null;
}
return $scopedLock;
* @file
* @ingroup DifferenceEngine
*/
-use MediaWiki\MediaWikiServices;
-use MediaWiki\Shell\Shell;
+
+use MediaWiki\Storage\RevisionRecord;
/**
- * @todo document
+ * DifferenceEngine is responsible for rendering the difference between two revisions as HTML.
+ * This includes interpreting URL parameters, retrieving revision data, checking access permissions,
+ * selecting and invoking the diff generator class for the individual slots, doing post-processing
+ * on the generated diff, adding the rest of the HTML (such as headers) and writing the whole thing
+ * to OutputPage.
+ *
+ * DifferenceEngine can be subclassed by extensions, by customizing
+ * ContentHandler::createDifferenceEngine; the content handler will be selected based on the
+ * content model of the main slot (of the new revision, when the two are different).
+ * That might change after PageTypeHandler gets introduced.
+ *
+ * In the past, the class was also used for slot-level diff generation, and extensions might still
+ * subclass it and add such functionality. When that is the case (sepcifically, when a
+ * ContentHandler returns a standard SlotDiffRenderer but a nonstandard DifferenceEngine)
+ * DifferenceEngineSlotDiffRenderer will be used to convert the old behavior into the new one.
+ *
* @ingroup DifferenceEngine
+ *
+ * @todo This class is huge and poorly defined. It should be split into a controller responsible
+ * for interpreting query parameters, retrieving data and checking permissions; and a HTML renderer.
*/
class DifferenceEngine extends ContextSource {
*/
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;
-
- /** @var Content|null */
- protected $mOldContent;
-
- /** @var Content|null */
- protected $mNewContent;
+ /**
+ * Old revision (left pane).
+ * Allowed to be an unsaved revision, unlikely that's ever needed though.
+ * 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|false
+ */
+ protected $mOldRev;
- /** @var Language */
- protected $mDiffLang;
+ /**
+ * New revision (right pane).
+ * Note that this might be an unsaved revision (e.g. for edit preview).
+ * 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 $mNewRev;
- /** @var Title */
+ /**
+ * Title of $mOldRev or null if the old revision does not exist or does not belong to a page.
+ * Since 1.32 public access is deprecated and the property can be null.
+ * @var Title|null
+ */
protected $mOldPage;
- /** @var Title */
+ /**
+ * Title of $mNewRev or null if the new revision does not exist or does not belong to a page.
+ * Since 1.32 public access is deprecated and the property can be null.
+ * @var Title|null
+ */
protected $mNewPage;
- /** @var Revision|null */
- public $mOldRev;
+ /**
+ * Change tags of $mOldRev or null if it does not exist / is not saved.
+ * @var string[]|null
+ */
+ private $mOldTags;
- /** @var Revision|null */
- public $mNewRev;
+ /**
+ * 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.
+ * This property is set to the content of the main slot, but not actually used for the main diff.
+ */
+ private $mOldContent;
+
+ /**
+ * @var Content|null
+ * @deprecated since 1.32, content slots are now handled by the corresponding SlotDiffRenderer.
+ * This property is set to the content of the main slot, but not actually used for the main diff.
+ */
+ private $mNewContent;
+
+ /** @var Language */
+ protected $mDiffLang;
/** @var bool Have the revisions IDs been loaded */
private $mRevisionsIdsLoaded = false;
/**
* Was the content overridden via setContent()?
- * If the content was overridden, most internal state (e.g. mOldid or mOldRev) should be ignored.
+ * If the content was overridden, most internal state (e.g. mOldid or mOldRev) should be ignored
+ * and only mOldContent and mNewContent is reliable.
+ * (Note that setRevisions() does not set this flag as in that case all properties are
+ * overriden and remain consistent with each other, so no special handling is needed.)
* @var bool
*/
protected $isContentOverridden = false;
/** @var bool Refresh the diff cache */
protected $mRefreshCache = false;
+ /** @var SlotDiffRenderer[] DifferenceEngine classes for the slots, keyed by role name. */
+ protected $slotDiffRenderers = null;
+
+ /**
+ * Temporary hack for B/C while slot diff related methods of DifferenceEngine are being
+ * deprecated. When true, we are inside a DifferenceEngineSlotDiffRenderer and
+ * $slotDiffRenderers should not be used.
+ * @var bool
+ */
+ protected $isSlotDiffRenderer = false;
+
/**#@-*/
/**
) {
$this->deprecatePublicProperty( 'mOldid', '1.32', __CLASS__ );
$this->deprecatePublicProperty( 'mNewid', '1.32', __CLASS__ );
+ $this->deprecatePublicProperty( 'mOldRev', '1.32', __CLASS__ );
+ $this->deprecatePublicProperty( 'mNewRev', '1.32', __CLASS__ );
$this->deprecatePublicProperty( 'mOldPage', '1.32', __CLASS__ );
$this->deprecatePublicProperty( 'mNewPage', '1.32', __CLASS__ );
$this->deprecatePublicProperty( 'mOldContent', '1.32', __CLASS__ );
}
/**
+ * @return SlotDiffRenderer[] Diff renderers for each slot, keyed by role name.
+ * Includes slots only present in one of the revisions.
+ */
+ protected function getSlotDiffRenderers() {
+ if ( $this->isSlotDiffRenderer ) {
+ throw new LogicException( __METHOD__ . ' called in slot diff renderer mode' );
+ }
+
+ if ( $this->slotDiffRenderers === null ) {
+ if ( !$this->loadRevisionData() ) {
+ return [];
+ }
+
+ $slotContents = $this->getSlotContents();
+ $this->slotDiffRenderers = array_map( function ( $contents ) {
+ /** @var $content Content */
+ $content = $contents['new'] ?: $contents['old'];
+ return $content->getContentHandler()->getSlotDiffRenderer( $this->getContext() );
+ }, $slotContents );
+ }
+ return $this->slotDiffRenderers;
+ }
+
+ /**
+ * Mark this DifferenceEngine as a slot renderer (as opposed to a page renderer).
+ * This is used in legacy mode when the DifferenceEngine is wrapped in a
+ * DifferenceEngineSlotDiffRenderer.
+ * @internal For use by DifferenceEngineSlotDiffRenderer only.
+ */
+ public function markAsSlotDiffRenderer() {
+ $this->isSlotDiffRenderer = true;
+ }
+
+ /**
+ * Get the old and new content objects for all slots.
+ * This method does not do any permission checks.
+ * @return array [ role => [ 'old' => SlotRecord|null, 'new' => SlotRecord|null ], ... ]
+ */
+ protected function getSlotContents() {
+ if ( $this->isContentOverridden ) {
+ return [
+ 'main' => [
+ 'old' => $this->mOldContent,
+ 'new' => $this->mNewContent,
+ ]
+ ];
+ } elseif ( !$this->loadRevisionData() ) {
+ return [];
+ }
+
+ $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
+ // 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 ) {
+ $slots[$role] = [
+ 'old' => isset( $oldSlots[$role] ) ? $oldSlots[$role]->getContent() : null,
+ 'new' => isset( $newSlots[$role] ) ? $newSlots[$role]->getContent() : null,
+ ];
+ }
+ // move main slot to front
+ if ( isset( $slots['main'] ) ) {
+ $slots = [ 'main' => $slots['main'] ] + $slots;
+ }
+ return $slots;
+ }
+
+ /**
+ * Set reduced line numbers mode.
+ * When set, line X is not displayed when X is 1, for example to increase readability and
+ * conserve space with many small diffs.
* @param bool $value
*/
public function setReducedLineNumbers( $value = true ) {
}
/**
- * @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 $this->mNewid;
}
+ /**
+ * Get the left side of the diff.
+ * Could be null when the first revision of the page is diffed to 'prev' (or in the case of
+ * load failure).
+ * @return RevisionRecord|null
+ */
+ public function getOldRevision() {
+ return $this->mOldRev ? $this->mOldRev->getRevisionRecord() : null;
+ }
+
+ /**
+ * Get the right side of the diff.
+ * Should not be null but can still happen in the case of load failure.
+ * @return RevisionRecord|null
+ */
+ public function getNewRevision() {
+ return $this->mNewRev ? $this->mNewRev->getRevisionRecord() : null;
+ }
+
/**
* Look up a special:Undelete link to the given deleted revision id,
* as a workaround for being unable to load deleted diffs in currently.
}
$user = $this->getUser();
- $permErrors = $this->mNewPage->getUserPermissionsErrors( 'read', $user );
- if ( $this->mOldPage ) { # mOldPage might not be set, see below.
+ $permErrors = [];
+ if ( $this->mNewPage ) {
+ $permErrors = $this->mNewPage->getUserPermissionsErrors( 'read', $user );
+ }
+ if ( $this->mOldPage ) {
$permErrors = wfMergeErrorArrays( $permErrors,
$this->mOldPage->getUserPermissionsErrors( 'read', $user ) );
}
# a diff between a version V and its previous version V' AND the version V
# is the first version of that article. In that case, V' does not exist.
if ( $this->mOldRev === false ) {
- $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
+ if ( $this->mNewPage ) {
+ $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
+ }
$samePage = true;
$oldHeader = '';
// Allow extensions to change the $oldHeader variable
} else {
Hooks::run( 'DiffViewHeader', [ $this, $this->mOldRev, $this->mNewRev ] );
- if ( $this->mNewPage->equals( $this->mOldPage ) ) {
+ if ( !$this->mOldPage || !$this->mNewPage ) {
+ // XXX say something to the user?
+ $samePage = false;
+ } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
$out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
$samePage = true;
} else {
$samePage = false;
}
- if ( $samePage && $this->mNewPage->quickUserCan( 'edit', $user ) ) {
+ if ( $samePage && $this->mNewPage && $this->mNewPage->quickUserCan( 'edit', $user ) ) {
if ( $this->mNewRev->isCurrent() && $this->mNewPage->userCan( 'rollback', $user ) ) {
$rollbackLink = Linker::generateRollback( $this->mNewRev, $this->getContext() );
if ( $rollbackLink ) {
}
# Make "previous revision link"
- if ( $samePage && $this->mOldRev->getPrevious() ) {
+ if ( $samePage && $this->mOldPage && $this->mOldRev->getPrevious() ) {
$prevlink = Linker::linkKnown(
$this->mOldPage,
$this->msg( 'previousdiff' )->escaped(),
# Make "next revision link"
# Skip next link on the top revision
- if ( $samePage && !$this->mNewRev->isCurrent() ) {
+ if ( $samePage && $this->mNewPage && !$this->mNewRev->isCurrent() ) {
$nextlink = Linker::linkKnown(
$this->mNewPage,
$this->msg( 'nextdiff' )->escaped(),
if ( $this->mMarkPatrolledLink === null ) {
$linkInfo = $this->getMarkPatrolledLinkInfo();
// If false, there is no patrol link needed/allowed
- if ( !$linkInfo ) {
+ if ( !$linkInfo || !$this->mNewPage ) {
$this->mMarkPatrolledLink = '';
} else {
$this->mMarkPatrolledLink = ' <span class="patrollink" data-mw="interface">[' .
// Prepare a change patrol link, if applicable
if (
// Is patrolling enabled and the user allowed to?
- $wgUseRCPatrol && $this->mNewPage->quickUserCan( 'patrol', $user ) &&
+ $wgUseRCPatrol && $this->mNewPage && $this->mNewPage->quickUserCan( 'patrol', $user ) &&
// Only do this if the revision isn't more than 6 hours older
// than the Max RC age (6h because the RC might not be cleaned out regularly)
RecentChange::isInRCLifespan( $this->mNewRev->getTimestamp(), 21600 )
# Page content may be handled by a hooked call instead...
if ( Hooks::run( 'ArticleContentOnDiff', [ $this, $out ] ) ) {
$this->loadNewText();
+ if ( !$this->mNewPage ) {
+ // New revision is unsaved; bail out.
+ // TODO in theory rendering the new revision is a meaningful thing to do
+ // even if it's unsaved, but a lot of untangling is required to do it safely.
+ }
+
$out->setRevisionId( $this->mNewid );
$out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
$out->setArticleFlag( true );
* Add style sheets for diff display.
*/
public function showDiffStyle() {
- $this->getOutput()->addModuleStyles( 'mediawiki.diff.styles' );
+ if ( !$this->isSlotDiffRenderer ) {
+ $this->getOutput()->addModuleStyles( 'mediawiki.diff.styles' );
+ foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
+ $slotDiffRenderer->addModules( $this->getOutput() );
+ }
+ }
}
/**
public function getDiffBody() {
$this->mCacheHit = true;
// Check if the diff should be hidden from this user
- if ( !$this->loadRevisionData() ) {
- return false;
- } elseif ( $this->mOldRev &&
- !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
- ) {
- return false;
- } elseif ( $this->mNewRev &&
- !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
- ) {
- return false;
- }
- // Short-circuit
- if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev
- && $this->mOldRev->getId() == $this->mNewRev->getId() )
- ) {
- if ( Hooks::run( 'DifferenceEngineShowEmptyOldContent', [ $this ] ) ) {
- return '';
+ if ( !$this->isContentOverridden ) {
+ if ( !$this->loadRevisionData() ) {
+ return false;
+ } elseif ( $this->mOldRev &&
+ !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
+ ) {
+ return false;
+ } elseif ( $this->mNewRev &&
+ !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
+ ) {
+ return false;
+ }
+ // Short-circuit
+ if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev &&
+ $this->mOldRev->getId() && $this->mOldRev->getId() == $this->mNewRev->getId() )
+ ) {
+ if ( Hooks::run( 'DifferenceEngineShowEmptyOldContent', [ $this ] ) ) {
+ return '';
+ }
}
}
+
// Cacheable?
$key = false;
$cache = ObjectCache::getMainWANInstance();
return false;
}
- $difftext = $this->generateContentDiffBody( $this->mOldContent, $this->mNewContent );
+ $difftext = '';
+ // We've checked for revdelete at the beginning of this method; it's OK to ignore
+ // read permissions here.
+ $slotContents = $this->getSlotContents();
+ foreach ( $this->getSlotDiffRenderers() as $role => $slotDiffRenderer ) {
+ $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role]['old'],
+ $slotContents[$role]['new'] );
+ if ( $slotDiff && $role !== 'main' ) {
+ // TODO use human-readable role name at least
+ $slotTitle = $role;
+ $difftext .= $this->getSlotHeader( $slotTitle );
+ }
+ $difftext .= $slotDiff;
+ }
// Avoid PHP 7.1 warning from passing $this by reference
$diffEngine = $this;
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).
+ *
+ * @param string $headerText The text of the header
+ * @return string
+ *
+ */
+ protected function getSlotHeader( $headerText ) {
+ // The old revision is missing on oldid=<first>&diff=prev; only 2 columns in that case.
+ $columnCount = $this->mOldRev ? 4 : 2;
+ $userLang = $this->getLanguage()->getHtmlCode();
+ return Html::rawElement( 'tr', [ 'class' => 'mw-diff-slot-header', 'lang' => $userLang ],
+ Html::element( 'th', [ 'colspan' => $columnCount ], $headerText ) );
+ }
+
/**
* Returns the cache key for diff body text or content.
*
$params[] = $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' );
}
+ if ( !$this->isSlotDiffRenderer ) {
+ foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
+ $params = array_merge( $params, $slotDiffRenderer->getExtraCacheKeys() );
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * Implements DifferenceEngineSlotDiffRenderer::getExtraCacheKeys(). Only used when
+ * DifferenceEngine is wrapped in DifferenceEngineSlotDiffRenderer.
+ * @return array
+ * @internal for use by DifferenceEngineSlotDiffRenderer only
+ * @deprecated
+ */
+ public function getExtraCacheKeys() {
+ // This method is called when the DifferenceEngine is used for a slot diff. We only care
+ // about special things, not the revision IDs, which are added to the cache key by the
+ // page-level DifferenceEngine, and which might not have a valid value for this object.
+ $this->mOldid = 123456789;
+ $this->mNewid = 987654321;
+
+ // This will repeat a bunch of unnecessary key fields for each slot. Not nice but harmless.
+ $cacheString = $this->getDiffBodyCacheKey();
+ if ( $cacheString ) {
+ return [ $cacheString ];
+ }
+
+ $params = $this->getDiffBodyCacheKeyParams();
+
+ // Try to get rid of the standard keys to keep the cache key human-readable:
+ // call the getDiffBodyCacheKeyParams implementation of the base class, and if
+ // the child class includes the same keys, drop them.
+ // Uses an obscure PHP feature where static calls to non-static methods are allowed
+ // as long as we are already in a non-static method of the same class, and the call context
+ // ($this) will be inherited.
+ // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed
+ $standardParams = DifferenceEngine::getDiffBodyCacheKeyParams();
+ if ( array_slice( $params, 0, count( $standardParams ) ) === $standardParams ) {
+ $params = array_slice( $params, count( $standardParams ) );
+ }
+
return $params;
}
/**
* Generate a diff, no caching.
*
- * This implementation uses generateTextDiffBody() to generate a diff based on the default
- * serialization of the given Content objects. This will fail if $old or $new are not
- * instances of TextContent.
- *
- * Subclasses may override this to provide a different rendering for the diff,
- * perhaps taking advantage of the content's native form. This is required for all content
- * models that are not text based.
- *
* @since 1.21
*
* @param Content $old Old content
* @param Content $new New content
*
- * @throws MWException If old or new content is not an instance of TextContent.
+ * @throws Exception If old or new content is not an instance of TextContent.
* @return bool|string
+ *
+ * @deprecated since 1.32, use a SlotDiffRenderer instead.
*/
public function generateContentDiffBody( Content $old, Content $new ) {
- if ( !( $old instanceof TextContent ) ) {
- throw new MWException( "Diff not implemented for " . get_class( $old ) . "; " .
- "override generateContentDiffBody to fix this." );
- }
-
- if ( !( $new instanceof TextContent ) ) {
- throw new MWException( "Diff not implemented for " . get_class( $new ) . "; "
- . "override generateContentDiffBody to fix this." );
- }
-
- $otext = $old->serialize();
- $ntext = $new->serialize();
-
- return $this->generateTextDiffBody( $otext, $ntext );
+ $slotDiffRenderer = $new->getContentHandler()->getSlotDiffRenderer( $this->getContext() );
+ if (
+ $slotDiffRenderer instanceof DifferenceEngineSlotDiffRenderer
+ && $this->isSlotDiffRenderer
+ ) {
+ // Oops, we are just about to enter an infinite loop (the slot-level DifferenceEngine
+ // called a DifferenceEngineSlotDiffRenderer that wraps the same DifferenceEngine class).
+ // This will happen when a content model has no custom slot diff renderer, it does have
+ // a custom difference engine, but that does not override this method.
+ throw new Exception( get_class( $this ) . ': could not maintain backwards compatibility. '
+ . 'Please use a SlotDiffRenderer.' );
+ }
+ return $slotDiffRenderer->getDiff( $old, $new ) . $this->getDebugString();
}
/**
* Generate a diff, no caching
*
- * @todo move this to TextDifferenceEngine, make DifferenceEngine abstract. At some point.
- *
* @param string $otext Old text, must be already segmented
* @param string $ntext New text, must be already segmented
*
+ * @throws Exception If content handling for text content is configured in a way
+ * that makes maintaining B/C hard.
* @return bool|string
+ *
+ * @deprecated since 1.32, use a TextSlotDiffRenderer instead.
*/
public function generateTextDiffBody( $otext, $ntext ) {
- $diff = function () use ( $otext, $ntext ) {
- $time = microtime( true );
-
- $result = $this->textDiff( $otext, $ntext );
-
- $time = intval( ( microtime( true ) - $time ) * 1000 );
- MediaWikiServices::getInstance()->getStatsdDataFactory()->timing( 'diff_time', $time );
- // Log requests slower than 99th percentile
- if ( $time > 100 && $this->mOldPage && $this->mNewPage ) {
- wfDebugLog( 'diff',
- "$time ms diff: {$this->mOldid} -> {$this->mNewid} {$this->mNewPage}" );
- }
-
- return $result;
- };
-
- /**
- * @param Status $status
- * @throws FatalError
- */
- $error = function ( $status ) {
- throw new FatalError( $status->getWikiText() );
- };
-
- // Use PoolCounter if the diff looks like it can be expensive
- if ( strlen( $otext ) + strlen( $ntext ) > 20000 ) {
- $work = new PoolCounterWorkViaCallback( 'diff',
- md5( $otext ) . md5( $ntext ),
- [ 'doWork' => $diff, 'error' => $error ]
- );
- return $work->execute();
- }
-
- return $diff();
+ $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
+ ->getSlotDiffRenderer( $this->getContext() );
+ if ( !( $slotDiffRenderer instanceof TextSlotDiffRenderer ) ) {
+ // Someone used the GetSlotDiffRenderer hook to replace the renderer.
+ // This is too unlikely to happen to bother handling properly.
+ throw new Exception( 'The slot diff renderer for text content should be a '
+ . 'TextSlotDiffRenderer subclass' );
+ }
+ return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
}
/**
* Process $wgExternalDiffEngine and get a sane, usable engine
*
* @return bool|string 'wikidiff2', path to an executable, or false
+ * @internal For use by this class and TextSlotDiffRenderer only.
*/
- private function getEngine() {
+ public static function getEngine() {
global $wgExternalDiffEngine;
// We use the global here instead of Config because we write to the value,
// and Config is not mutable.
*
* @param string $otext Old text, must be already segmented
* @param string $ntext New text, must be already segmented
+ *
+ * @throws Exception If content handling for text content is configured in a way
+ * that makes maintaining B/C hard.
* @return bool|string
+ *
+ * @deprecated since 1.32, use a TextSlotDiffRenderer instead.
*/
protected function textDiff( $otext, $ntext ) {
- $otext = str_replace( "\r\n", "\n", $otext );
- $ntext = str_replace( "\r\n", "\n", $ntext );
-
- $engine = $this->getEngine();
-
- // Better external diff engine, the 2 may some day be dropped
- // This one does the escaping and segmenting itself
- if ( $engine === 'wikidiff2' ) {
- $wikidiff2Version = phpversion( 'wikidiff2' );
- if (
- $wikidiff2Version !== false &&
- version_compare( $wikidiff2Version, '1.5.0', '>=' )
- ) {
- $text = wikidiff2_do_diff(
- $otext,
- $ntext,
- 2,
- $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' )
- );
- } else {
- // Don't pass the 4th parameter for compatibility with older versions of wikidiff2
- $text = wikidiff2_do_diff(
- $otext,
- $ntext,
- 2
- );
-
- // Log a warning in case the configuration value is set to not silently ignore it
- if ( $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' ) > 0 ) {
- wfLogWarning( '$wgWikiDiff2MovedParagraphDetectionCutoff is set but has no
- effect since the used version of WikiDiff2 does not support it.' );
- }
- }
-
- $text .= $this->debug( 'wikidiff2' );
-
- return $text;
- } elseif ( $engine !== false ) {
- # Diff via the shell
- $tmpDir = wfTempDir();
- $tempName1 = tempnam( $tmpDir, 'diff_' );
- $tempName2 = tempnam( $tmpDir, 'diff_' );
-
- $tempFile1 = fopen( $tempName1, "w" );
- if ( !$tempFile1 ) {
- return false;
- }
- $tempFile2 = fopen( $tempName2, "w" );
- if ( !$tempFile2 ) {
- return false;
- }
- fwrite( $tempFile1, $otext );
- fwrite( $tempFile2, $ntext );
- fclose( $tempFile1 );
- fclose( $tempFile2 );
- $cmd = [ $engine, $tempName1, $tempName2 ];
- $result = Shell::command( $cmd )
- ->execute();
- $exitCode = $result->getExitCode();
- if ( $exitCode !== 0 ) {
- throw new Exception( "External diff command returned code {$exitCode}. Stderr: "
- . wfEscapeWikiText( $result->getStderr() )
- );
- }
- $difftext = $result->getStdout();
- $difftext .= $this->debug( "external $engine" );
- unlink( $tempName1 );
- unlink( $tempName2 );
-
- return $difftext;
- }
-
- # Native PHP diff
- $contLang = MediaWikiServices::getInstance()->getContentLanguage();
- $ota = explode( "\n", $contLang->segmentForDiff( $otext ) );
- $nta = explode( "\n", $contLang->segmentForDiff( $ntext ) );
- $diffs = new Diff( $ota, $nta );
- $formatter = new TableDiffFormatter();
- $difftext = $contLang->unsegmentForDiff( $formatter->format( $diffs ) );
- $difftext .= $this->debug( 'native PHP' );
-
- return $difftext;
+ $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
+ ->getSlotDiffRenderer( $this->getContext() );
+ if ( !( $slotDiffRenderer instanceof TextSlotDiffRenderer ) ) {
+ // Someone used the GetSlotDiffRenderer hook to replace the renderer.
+ // This is too unlikely to happen to bother handling properly.
+ throw new Exception( 'The slot diff renderer for text content should be a '
+ . 'TextSlotDiffRenderer subclass' );
+ }
+ return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
}
/**
" -->\n";
}
+ private function getDebugString() {
+ $engine = self::getEngine();
+ if ( $engine === 'wikidiff2' ) {
+ return $this->debug( 'wikidiff2' );
+ } elseif ( $engine === false ) {
+ return $this->debug( 'native PHP' );
+ } else {
+ return $this->debug( "external $engine" );
+ }
+ }
+
/**
* Localise diff output
*
* @return string
*/
public function getMultiNotice() {
- if ( !is_object( $this->mOldRev ) || !is_object( $this->mNewRev ) ) {
- return '';
- } elseif ( !$this->mOldPage->equals( $this->mNewPage ) ) {
- // Comparing two different pages? Count would be meaningless.
+ // The notice only make sense if we are diffing two saved revisions of the same page.
+ if (
+ !$this->mOldRev || !$this->mNewRev
+ || !$this->mOldPage || !$this->mNewPage
+ || !$this->mOldPage->equals( $this->mNewPage )
+ ) {
return '';
}
* @param Content $oldContent
* @param Content $newContent
* @since 1.21
+ * @deprecated since 1.32, use setRevisions or ContentHandler::getSlotDiffRenderer.
*/
public function setContent( Content $oldContent, Content $newContent ) {
$this->mOldContent = $oldContent;
$this->mTextLoaded = 2;
$this->mRevisionsLoaded = true;
$this->isContentOverridden = true;
+ $this->slotDiffRenderers = null;
+ }
+
+ /**
+ * Use specified text instead of loading from the database.
+ * @param RevisionRecord|null $oldRevision
+ * @param RevisionRecord $newRevision
+ */
+ public function setRevisions(
+ RevisionRecord $oldRevision = null, RevisionRecord $newRevision
+ ) {
+ if ( $oldRevision ) {
+ $this->mOldRev = new Revision( $oldRevision );
+ $this->mOldid = $oldRevision->getId();
+ $this->mOldPage = Title::newFromLinkTarget( $oldRevision->getPageAsLinkTarget() );
+ // This method is meant for edit diffs and such so there is no reason to provide a
+ // revision that's not readable to the user, but check it just in case.
+ $this->mOldContent = $oldRevision ? $oldRevision->getContent( 'main',
+ RevisionRecord::FOR_THIS_USER, $this->getUser() ) : null;
+ } else {
+ $this->mOldPage = null;
+ $this->mOldRev = $this->mOldid = false;
+ }
+ $this->mNewRev = new Revision( $newRevision );
+ $this->mNewid = $newRevision->getId();
+ $this->mNewPage = Title::newFromLinkTarget( $newRevision->getPageAsLinkTarget() );
+ $this->mNewContent = $newRevision->getContent( 'main',
+ RevisionRecord::FOR_THIS_USER, $this->getUser() );
+
+ $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded = true;
+ $this->mTextLoaded = !!$oldRevision + 1;
+ $this->isContentOverridden = false;
+ $this->slotDiffRenderers = null;
}
/**
* @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
// Update the new revision ID in case it was 0 (makes life easier doing UI stuff)
$this->mNewid = $this->mNewRev->getId();
- $this->mNewPage = $this->mNewRev->getTitle();
+ if ( $this->mNewid ) {
+ $this->mNewPage = $this->mNewRev->getTitle();
+ } else {
+ $this->mNewPage = null;
+ }
// Load the old revision object
$this->mOldRev = false;
return false;
}
- if ( $this->mOldRev ) {
+ if ( $this->mOldRev && $this->mOldRev->getId() ) {
$this->mOldPage = $this->mOldRev->getTitle();
+ } else {
+ $this->mOldPage = null;
}
// Load tags information for both revisions
/**
* 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;
--- /dev/null
+<?php
+/**
+ * Adapter for turning a DifferenceEngine into a SlotDiffRenderer.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup DifferenceEngine
+ */
+
+/**
+ * B/C adapter for turning a DifferenceEngine into a SlotDiffRenderer.
+ * Before SlotDiffRenderer was introduced, getDiff() functionality was provided by DifferenceEngine
+ * subclasses. Convert such a subclass into a SlotDiffRenderer.
+ * @deprecated
+ * @ingroup DifferenceEngine
+ */
+class DifferenceEngineSlotDiffRenderer extends SlotDiffRenderer {
+
+ /** @var DifferenceEngine */
+ private $differenceEngine;
+
+ public function __construct( DifferenceEngine $differenceEngine ) {
+ $this->differenceEngine = clone $differenceEngine;
+
+ // Set state to loaded. This should not matter to any of the methods invoked by
+ // the adapter, but just in case a load does get triggered somehow, make sure it's a no-op.
+ $fakeContent = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT )->makeEmptyContent();
+ $this->differenceEngine->setContent( $fakeContent, $fakeContent );
+
+ $this->differenceEngine->markAsSlotDiffRenderer();
+ }
+
+ /** @inheritDoc */
+ public function getDiff( Content $oldContent = null, Content $newContent = null ) {
+ if ( !$oldContent && !$newContent ) {
+ throw new InvalidArgumentException( '$oldContent and $newContent cannot both be null' );
+ }
+ if ( !$oldContent || !$newContent ) {
+ $someContent = $newContent ?: $oldContent;
+ $emptyContent = $someContent->getContentHandler()->makeEmptyContent();
+ $oldContent = $oldContent ?: $emptyContent;
+ $newContent = $newContent ?: $emptyContent;
+ }
+ return $this->differenceEngine->generateContentDiffBody( $oldContent, $newContent );
+ }
+
+ public function addModules( OutputPage $output ) {
+ $oldContext = null;
+ if ( $output !== $this->differenceEngine->getOutput() ) {
+ $oldContext = $this->differenceEngine->getContext();
+ $newContext = new DerivativeContext( $oldContext );
+ $newContext->setOutput( $output );
+ $this->differenceEngine->setContext( $newContext );
+ }
+ $this->differenceEngine->showDiffStyle();
+ if ( $oldContext ) {
+ $this->differenceEngine->setContext( $oldContext );
+ }
+ }
+
+ public function getExtraCacheKeys() {
+ return $this->differenceEngine->getExtraCacheKeys();
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Renders a diff for a single slot.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup DifferenceEngine
+ */
+
+/**
+ * Renders a diff for a single slot (that is, a diff between two content objects).
+ *
+ * Callers should obtain this class by invoking ContentHandler::getSlotDiffRendererClass
+ * on the content handler of the new content object (ie. the one shown on the right side
+ * of the diff), or of the old one if the new one does not exist.
+ *
+ * The default implementation just does a text diff on the native text representation.
+ * Content handler extensions can subclass this to provide a more appropriate diff method by
+ * overriding ContentHandler::getSlotDiffRendererClass. Other extensions that want to interfere
+ * with diff generation in some way can use the GetSlotDiffRenderer hook.
+ *
+ * @ingroup DifferenceEngine
+ */
+abstract class SlotDiffRenderer {
+
+ /**
+ * Get a diff between two content objects. One of them might be null (meaning a slot was
+ * created or removed), but both cannot be. $newContent (or if it's null then $oldContent)
+ * must have the same content model that was used to obtain this diff renderer.
+ * @param Content|null $oldContent
+ * @param Content|null $newContent
+ * @return string
+ */
+ abstract public function getDiff( Content $oldContent = null, Content $newContent = null );
+
+ /**
+ * Add modules needed for correct styling/behavior of the diff.
+ * @param OutputPage $output
+ */
+ public function addModules( OutputPage $output ) {
+ }
+
+ /**
+ * Return any extra keys to split the diff cache by.
+ * @return array
+ */
+ public function getExtraCacheKeys() {
+ return [];
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Renders a slot diff by doing a text diff on the native representation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup DifferenceEngine
+ */
+
+use MediaWiki\Shell\Shell;
+use Wikimedia\Assert\Assert;
+
+/**
+ * Renders a slot diff by doing a text diff on the native representation.
+ *
+ * If you want to use this without content objects (to call getTextDiff() on some
+ * non-content-related texts), obtain an instance with
+ * ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
+ * ->getSlotDiffRenderer( RequestContext::getMain() )
+ *
+ * @ingroup DifferenceEngine
+ */
+class TextSlotDiffRenderer extends SlotDiffRenderer {
+
+ /** Use the PHP diff implementation (DiffEngine). */
+ const ENGINE_PHP = 'php';
+
+ /** Use the wikidiff2 PHP module. */
+ const ENGINE_WIKIDIFF2 = 'wikidiff2';
+
+ /** Use an external executable. */
+ const ENGINE_EXTERNAL = 'external';
+
+ /** @var IBufferingStatsdDataFactory|null */
+ private $statsdDataFactory;
+
+ /** @var Language|null The language this content is in. */
+ private $language;
+
+ /**
+ * Number of paragraph moves the algorithm should attempt to detect.
+ * Only used with the wikidiff2 engine.
+ * @var int
+ * @see $wgWikiDiff2MovedParagraphDetectionCutoff
+ */
+ private $wikiDiff2MovedParagraphDetectionCutoff = 0;
+
+ /** @var string One of the ENGINE_* constants. */
+ private $engine = self::ENGINE_PHP;
+
+ /** @var string Path to an executable to be used as the diff engine. */
+ private $externalEngine;
+
+ /**
+ * Convenience helper to use getTextDiff without an instance.
+ * @param string $oldText
+ * @param string $newText
+ * @return string
+ */
+ public static function diff( $oldText, $newText ) {
+ /** @var $slotDiffRenderer TextSlotDiffRenderer */
+ $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
+ ->getSlotDiffRenderer( RequestContext::getMain() );
+ return $slotDiffRenderer->getTextDiff( $oldText, $newText );
+ }
+
+ public function setStatsdDataFactory( IBufferingStatsdDataFactory $statsdDataFactory ) {
+ $this->statsdDataFactory = $statsdDataFactory;
+ }
+
+ public function setLanguage( Language $language ) {
+ $this->language = $language;
+ }
+ /**
+ * @param int $cutoff
+ * @see $wgWikiDiff2MovedParagraphDetectionCutoff
+ */
+ public function setWikiDiff2MovedParagraphDetectionCutoff( $cutoff ) {
+ Assert::parameterType( 'integer', $cutoff, '$cutoff' );
+ $this->wikiDiff2MovedParagraphDetectionCutoff = $cutoff;
+ }
+
+ /**
+ * Set which diff engine to use.
+ * @param string $type One of the ENGINE_* constants.
+ * @param string|null $executable Path to an external exectable, only when type is ENGINE_EXTERNAL.
+ */
+ public function setEngine( $type, $executable = null ) {
+ $engines = [ self::ENGINE_PHP, self::ENGINE_WIKIDIFF2, self::ENGINE_EXTERNAL ];
+ Assert::parameter( in_array( $type, $engines, true ), '$type',
+ 'must be one of the TextSlotDiffRenderer::ENGINE_* constants' );
+ if ( $type === self::ENGINE_EXTERNAL ) {
+ Assert::parameter( is_string( $executable ) && is_executable( $executable ), '$executable',
+ 'must be a path to a valid executable' );
+ } else {
+ Assert::parameter( is_null( $executable ), '$executable',
+ 'must not be set unless $type is ENGINE_EXTERNAL' );
+ }
+ $this->engine = $type;
+ $this->externalEngine = $executable;
+ }
+
+ /** @inheritDoc */
+ public function getDiff( Content $oldContent = null, Content $newContent = null ) {
+ if ( !$oldContent && !$newContent ) {
+ throw new InvalidArgumentException( '$oldContent and $newContent cannot both be null' );
+ } elseif ( $oldContent && !( $oldContent instanceof TextContent ) ) {
+ throw new InvalidArgumentException( __CLASS__ . ' does not handle ' . get_class( $oldContent ) );
+ } elseif ( $newContent && !( $newContent instanceof TextContent ) ) {
+ throw new InvalidArgumentException( __CLASS__ . ' does not handle ' . get_class( $newContent ) );
+ }
+
+ if ( !$oldContent ) {
+ $oldContent = $newContent->getContentHandler()->makeEmptyContent();
+ } elseif ( !$newContent ) {
+ $newContent = $oldContent->getContentHandler()->makeEmptyContent();
+ }
+
+ $oldText = $oldContent->serialize();
+ $newText = $newContent->serialize();
+
+ return $this->getTextDiff( $oldText, $newText );
+ }
+
+ /**
+ * Diff the text representations of two content objects (or just two pieces of text in general).
+ * @param string $oldText
+ * @param string $newText
+ * @return string
+ */
+ public function getTextDiff( $oldText, $newText ) {
+ Assert::parameterType( 'string', $oldText, '$oldText' );
+ Assert::parameterType( 'string', $newText, '$newText' );
+
+ $diff = function () use ( $oldText, $newText ) {
+ $time = microtime( true );
+
+ $result = $this->getTextDiffInternal( $oldText, $newText );
+
+ $time = intval( ( microtime( true ) - $time ) * 1000 );
+ if ( $this->statsdDataFactory ) {
+ $this->statsdDataFactory->timing( 'diff_time', $time );
+ }
+
+ // TODO reimplement this using T142313
+ /*
+ // Log requests slower than 99th percentile
+ if ( $time > 100 && $this->mOldPage && $this->mNewPage ) {
+ wfDebugLog( 'diff',
+ "$time ms diff: {$this->mOldid} -> {$this->mNewid} {$this->mNewPage}" );
+ }
+ */
+
+ return $result;
+ };
+
+ /**
+ * @param Status $status
+ * @throws FatalError
+ */
+ $error = function ( $status ) {
+ throw new FatalError( $status->getWikiText() );
+ };
+
+ // Use PoolCounter if the diff looks like it can be expensive
+ if ( strlen( $oldText ) + strlen( $newText ) > 20000 ) {
+ $work = new PoolCounterWorkViaCallback( 'diff',
+ md5( $oldText ) . md5( $newText ),
+ [ 'doWork' => $diff, 'error' => $error ]
+ );
+ return $work->execute();
+ }
+
+ return $diff();
+ }
+
+ /**
+ * Diff the text representations of two content objects (or just two pieces of text in general).
+ * This does the actual diffing, getTextDiff() wraps it with logging and resource limiting.
+ * @param string $oldText
+ * @param string $newText
+ * @return string
+ * @throws Exception
+ */
+ protected function getTextDiffInternal( $oldText, $newText ) {
+ // TODO move most of this into three parallel implementations of a text diff generator
+ // class, choose which one to use via dependecy injection
+
+ $oldText = str_replace( "\r\n", "\n", $oldText );
+ $newText = str_replace( "\r\n", "\n", $newText );
+
+ // Better external diff engine, the 2 may some day be dropped
+ // This one does the escaping and segmenting itself
+ if ( $this->engine === self::ENGINE_WIKIDIFF2 ) {
+ $wikidiff2Version = phpversion( 'wikidiff2' );
+ if (
+ $wikidiff2Version !== false &&
+ version_compare( $wikidiff2Version, '1.5.0', '>=' ) &&
+ version_compare( $wikidiff2Version, '1.8.0', '<' )
+ ) {
+ $text = wikidiff2_do_diff(
+ $oldText,
+ $newText,
+ 2,
+ $this->wikiDiff2MovedParagraphDetectionCutoff
+ );
+ } else {
+ // 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,
+ 2
+ );
+
+ // Log a warning in case the configuration value is set to not silently ignore it
+ if ( $this->wikiDiff2MovedParagraphDetectionCutoff > 0 ) {
+ wfLogWarning( '$wgWikiDiff2MovedParagraphDetectionCutoff is set but has no
+ effect since the used version of WikiDiff2 does not support it.' );
+ }
+ }
+
+ return $text;
+ } elseif ( $this->engine === self::ENGINE_EXTERNAL ) {
+ # Diff via the shell
+ $tmpDir = wfTempDir();
+ $tempName1 = tempnam( $tmpDir, 'diff_' );
+ $tempName2 = tempnam( $tmpDir, 'diff_' );
+
+ $tempFile1 = fopen( $tempName1, "w" );
+ if ( !$tempFile1 ) {
+ return false;
+ }
+ $tempFile2 = fopen( $tempName2, "w" );
+ if ( !$tempFile2 ) {
+ return false;
+ }
+ fwrite( $tempFile1, $oldText );
+ fwrite( $tempFile2, $newText );
+ fclose( $tempFile1 );
+ fclose( $tempFile2 );
+ $cmd = [ $this->externalEngine, $tempName1, $tempName2 ];
+ $result = Shell::command( $cmd )
+ ->execute();
+ $exitCode = $result->getExitCode();
+ if ( $exitCode !== 0 ) {
+ throw new Exception( "External diff command returned code {$exitCode}. Stderr: "
+ . wfEscapeWikiText( $result->getStderr() )
+ );
+ }
+ $difftext = $result->getStdout();
+ unlink( $tempName1 );
+ unlink( $tempName2 );
+
+ return $difftext;
+ } elseif ( $this->engine === self::ENGINE_PHP ) {
+ if ( $this->language ) {
+ $oldText = $this->language->segmentForDiff( $oldText );
+ $newText = $this->language->segmentForDiff( $newText );
+ }
+ $ota = explode( "\n", $oldText );
+ $nta = explode( "\n", $newText );
+ $diffs = new Diff( $ota, $nta );
+ $formatter = new TableDiffFormatter();
+ $difftext = $formatter->format( $diffs );
+ if ( $this->language ) {
+ $difftext = $this->language->unsegmentForDiff( $difftext );
+ }
+
+ return $difftext;
+ }
+ throw new LogicException( 'Invalid engine: ' . $this->engine );
+ }
+
+}
* '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.
* 'help-inline' -- Whether help text (defined using options above) will be shown
* inline after the input field, rather than in a popup.
* Defaults to true. Only used by OOUI form fields.
- * 'notice' -- message text for a message to use as a notice in the field.
- * Currently used by OOUI form fields only.
- * 'notice-messages' -- array of message keys/objects to use for notice.
- * Overrides 'notice'.
- * 'notice-message' -- message key or object to use as a notice.
+ * 'notice' -- (deprecated, use 'help' instead)
+ * 'notice-messages' -- (deprecated, use 'help-messages' instead)
+ * 'notice-message' -- (deprecated, use 'help-message' instead)
* 'required' -- passed through to the object, indicating that it
* is a required field.
* 'size' -- the length of text fields
if ( isset( $params['hide-if'] ) ) {
$this->mHideIf = $params['hide-if'];
}
+
+ if ( isset( $this->mParams['notice-message'] ) ) {
+ wfDeprecated( "'notice-message' parameter in HTMLForm", '1.32' );
+ }
+ if ( isset( $this->mParams['notice-messages'] ) ) {
+ wfDeprecated( "'notice-messages' parameter in HTMLForm", '1.32' );
+ }
+ if ( isset( $this->mParams['notice'] ) ) {
+ wfDeprecated( "'notice' parameter in HTMLForm", '1.32' );
+ }
}
/**
$error = new OOUI\HtmlSnippet( $error );
}
- $notices = $this->getNotices();
+ $notices = $this->getNotices( 'skip deprecation' );
foreach ( $notices as &$notice ) {
$notice = new OOUI\HtmlSnippet( $notice );
}
* Determine notices to display for the field.
*
* @since 1.28
+ * @deprecated since 1.32
+ * @param string $skipDeprecation Pass 'skip deprecation' to avoid the deprecation
+ * warning (since 1.32)
* @return string[]
*/
- public function getNotices() {
+ public function getNotices( $skipDeprecation = null ) {
+ if ( $skipDeprecation !== 'skip deprecation' ) {
+ wfDeprecated( __METHOD__, '1.32' );
+ }
+
$notices = [];
if ( isset( $this->mParams['notice-message'] ) ) {
$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]"
}
// Serialize links updates by page ID so they see each others' changes
$scopedLock = LinksUpdate::acquirePageLock( wfGetDB( DB_MASTER ), $pageId, 'job' );
+ if ( $scopedLock === null ) {
+ $this->setLastError( 'LinksUpdate already running for this page, try again later.' );
+ return false;
+ }
if ( WikiPage::newFromID( $pageId, WikiPage::READ_LATEST ) ) {
// The page was restored somehow or something went wrong
$dbw = $lbFactory->getMainLB()->getConnection( DB_MASTER );
/** @noinspection PhpUnusedLocalVariableInspection */
$scopedLock = LinksUpdate::acquirePageLock( $dbw, $page->getId(), 'job' );
+ if ( $scopedLock === null ) {
+ // Another job is already updating the page, likely for an older revision (T170596).
+ $this->setLastError( 'LinksUpdate already running for this page, try again later.' );
+ return false;
+ }
// Get the latest ID *after* acquirePageLock() flushed the transaction.
// This is used to detect edits/moves after loadPageData() but before the scope lock.
// The works around the chicken/egg problem of determining the scope lock key.
$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.
return $this->__call( __FUNCTION__, func_get_args() );
}
- public function open( $server, $user, $password, $dbName ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
public function fetchObject( $res ) {
return $this->__call( __FUNCTION__, func_get_args() );
}
/** @var int[] Prior flags member variable values */
private $priorFlags = [];
- /** @var object|string Class name or object With profileIn/profileOut methods */
+ /** @var mixed Class name or object With profileIn/profileOut methods */
protected $profiler;
/** @var TransactionProfiler */
protected $trxProfiler;
}
}
+ /**
+ * Open a new connection to the database (closing any existing one)
+ *
+ * @param string $server Database server host
+ * @param string $user Database user name
+ * @param string $password Database user password
+ * @param string $dbName Database name
+ * @return bool
+ * @throws DBConnectionError
+ */
+ abstract protected function open( $server, $user, $password, $dbName );
+
/**
* Construct a Database subclass instance given a database type and parameters
*
list( $phpCallback ) = $callback;
$phpCallback( $this );
} catch ( Exception $ex ) {
- $this->errorLogger( $ex );
+ ( $this->errorLogger )( $ex );
$e = $e ?: $ex;
}
}
* a wrapper. Nowadays, raw database objects are never exposed to external
* callers, so this is unnecessary in external code.
*
- * @param bool|ResultWrapper|resource|object $result
+ * @param bool|ResultWrapper|resource $result
* @return bool|ResultWrapper
*/
protected function resultObject( $result ) {
parent::__construct( $params );
}
- /**
- * Usually aborts on failure
- * @param string $server
- * @param string $user
- * @param string $password
- * @param string $dbName
- * @throws DBConnectionError
- * @return bool|resource|null
- */
- public function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName ) {
# Test for driver support, to avoid suppressed fatal error
if ( !function_exists( 'sqlsrv_connect' ) ) {
throw new DBConnectionError(
$this->opened = true;
- return $this->conn;
+ return (bool)$this->conn;
}
/**
}
/**
- * @param MssqlResultWrapper $res
+ * @param IResultWrapper $res
* @return stdClass
*/
public function fetchObject( $res ) {
}
/**
- * @param MssqlResultWrapper $res
+ * @param IResultWrapper $res
* @return array
*/
public function fetchRow( $res ) {
return 'mysql';
}
- /**
- * @param string $server
- * @param string $user
- * @param string $password
- * @param string $dbName
- * @throws Exception|DBConnectionError
- * @return bool
- */
- public function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName ) {
# Close/unset connection handle
$this->close();
return false;
}
- public function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName ) {
# Test for Postgres support, to avoid suppressed fatal error
if ( !function_exists( 'pg_connect' ) ) {
throw new DBConnectionError(
return false;
}
- /** Open an SQLite database and return a resource handle to it
- * NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
- *
- * @param string $server
- * @param string $user Unused
- * @param string $pass
- * @param string $dbName
- *
- * @throws DBConnectionError
- * @return bool
- */
- function open( $server, $user, $pass, $dbName ) {
+ protected function open( $server, $user, $pass, $dbName ) {
$this->close();
$fileName = self::generateFileName( $this->dbDir, $dbName );
if ( !is_readable( $fileName ) ) {
$this->conn = false;
throw new DBConnectionError( $this, "SQLite database not accessible" );
}
+ // Only $dbName is used, the other parameters are irrelevant for SQLite databases
$this->openFile( $fileName, $dbName );
return (bool)$this->conn;
*/
public function getType();
- /**
- * Open a new connection to the database (closing any existing one)
- *
- * @param string $server Database server host
- * @param string $user Database user name
- * @param string $password Database user password
- * @param string $dbName Database name
- * @return bool
- * @throws DBConnectionError
- */
- public function open( $server, $user, $password, $dbName );
-
/**
* Fetch the next row from the given result object, in object form.
* Fields can be retrieved with $row->fieldname, with fields acting like
* @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 ];
# with CSS (T37247)
$class = $this->mOptions->getWrapOutputClass();
if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) {
- $text = Html::rawElement( 'div', [ 'class' => $class ], $text );
+ $this->mOutput->addWrapperDivClass( $class );
}
$this->mOutput->setText( $text );
* 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;
}
/** @var int|null Assumed rev ID for {{REVISIONID}} if no revision is set */
private $mSpeculativeRevId;
+ /** string CSS classes to use for the wrapping div, stored in the array keys.
+ * If no class is given, no wrapper is added.
+ */
+ private $mWrapperDivClasses = [];
+
/** @var int Upper bound of expiry based on parse duration */
private $mMaxAdaptiveExpiry = INF;
* - enableSectionEditLinks: (bool) Include section edit links, assuming
* section edit link tokens are present in the HTML. Default is true,
* but might be statefully overridden.
- * - unwrap: (bool) Remove a wrapping mw-parser-output div. Default is false.
+ * - unwrap: (bool) Return text without a wrapper div. Default is false,
+ * meaning a wrapper div will be added if getWrapperDivClass() returns
+ * a non-empty string.
+ * - wrapperDivClass: (string) Wrap the output in a div and apply the given
+ * CSS class to that div. This overrides the output of getWrapperDivClass().
+ * Setting this to an empty string has the same effect as 'unwrap' => true.
* - deduplicateStyles: (bool) When true, which is the default, `<style>`
* tags with the `data-mw-deduplicate` attribute set are deduplicated by
* value of the attribute: all but the first will be replaced by `<link
'enableSectionEditLinks' => true,
'unwrap' => false,
'deduplicateStyles' => true,
+ 'wrapperDivClass' => $this->getWrapperDivClass(),
];
$text = $this->mText;
Hooks::runWithoutAbort( 'ParserOutputPostCacheTransform', [ $this, &$text, &$options ] );
- if ( $options['unwrap'] !== false ) {
- $start = Html::openElement( 'div', [
- 'class' => 'mw-parser-output'
- ] );
- $startLen = strlen( $start );
- $end = Html::closeElement( 'div' );
- $endPos = strrpos( $text, $end );
- $endLen = strlen( $end );
-
- if ( substr( $text, 0, $startLen ) === $start && $endPos !== false
- // if the closing div is followed by real content, bail out of unwrapping
- && preg_match( '/^(?>\s*<!--.*?-->)*\s*$/s', substr( $text, $endPos + $endLen ) )
- ) {
- $text = substr( $text, $startLen );
- $text = substr( $text, 0, $endPos - $startLen )
- . substr( $text, $endPos - $startLen + $endLen );
- }
+ if ( $options['wrapperDivClass'] !== '' && !$options['unwrap'] ) {
+ $text = Html::rawElement( 'div', [ 'class' => $options['wrapperDivClass'] ], $text );
}
if ( $options['enableSectionEditLinks'] ) {
return $text;
}
+ /**
+ * Add a CSS class to use for the wrapping div. If no class is given, no wrapper is added.
+ *
+ * @param string $class
+ */
+ public function addWrapperDivClass( $class ) {
+ $this->mWrapperDivClasses[$class] = true;
+ }
+
+ /**
+ * Clears the CSS class to use for the wrapping div, effectively disabling the wrapper div
+ * until addWrapperDivClass() is called.
+ */
+ public function clearWrapperDivClass() {
+ $this->mWrapperDivClasses = [];
+ }
+
+ /**
+ * Returns the class (or classes) to be used with the wrapper div for this otuput.
+ * If there is no wrapper class given, no wrapper div should be added.
+ * The wrapper div is added automatically by getText().
+ *
+ * @return string
+ */
+ public function getWrapperDivClass() {
+ return implode( ' ', array_keys( $this->mWrapperDivClasses ) );
+ }
+
/**
* @param int $id
* @since 1.28
'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 );
}
}
throw new MWException( __METHOD__ . ": skip function file not found: \"$localPath\"" );
}
$contents = $this->stripBom( file_get_contents( $localPath ) );
- if ( $this->getConfig()->get( 'ResourceLoaderValidateStaticJS' ) ) {
- $contents = $this->validateScriptFile( $localPath, $contents );
- }
return $contents;
}
throw new MWException( __METHOD__ . ": script file not found: \"$localPath\"" );
}
$contents = $this->stripBom( file_get_contents( $localPath ) );
- if ( $this->getConfig()->get( 'ResourceLoaderValidateStaticJS' ) ) {
- // Static files don't really need to be checked as often; unlike
- // on-wiki module they shouldn't change unexpectedly without
- // admin interference.
- $contents = $this->validateScriptFile( $fileName, $contents );
- }
$js .= $contents . "\n";
}
return $js;
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() {
* @ingroup Search
*/
abstract class SearchEngine {
+ const DEFAULT_SORT = 'relevance';
+
/** @var string */
public $prefix = '';
/** @var bool */
protected $showSuggestion = true;
- private $sort = 'relevance';
+ private $sort = self::DEFAULT_SORT;
/** @var array Feature values */
protected $features = [];
/**
* Get the valid sort directions. All search engines support 'relevance' but others
- * might support more. The default in all implementations should be 'relevance.'
+ * might support more. The default in all implementations must be 'relevance.'
*
* @since 1.25
* @return string[] the valid sort directions for setSort
*/
public function getValidSorts() {
- return [ 'relevance' ];
+ return [ self::DEFAULT_SORT ];
}
/**
/**
* 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() );
}
/**
$uTalkTitle,
$this->msg( 'newmessageslinkplural' )->params( $plural )->escaped(),
[],
- [ 'redirect' => 'no' ]
+ $uTalkTitle->isRedirect() ? [ 'redirect' => 'no' ] : []
);
$newMessagesDiffLink = Linker::linkKnown(
$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
[
'label' => $this->msg( 'movetalk' )->text(),
'help' => new OOUI\HtmlSnippet( $this->msg( 'movepagetalktext' )->parseAsBlock() ),
+ 'helpInline' => true,
'align' => 'inline',
- 'infusable' => true,
'id' => 'wpMovetalk-field',
]
);
*/
protected $fulltext;
+ /**
+ * @var string
+ */
+ protected $sort;
+
/**
* @var bool
*/
$this->setExtraParam( 'prefix', $this->mPrefix );
}
+ $this->sort = $request->getVal( 'sort', SearchEngine::DEFAULT_SORT );
+ if ( $this->sort !== SearchEngine::DEFAULT_SORT ) {
+ $this->setExtraParam( 'sort', $this->sort );
+ }
+
$user = $this->getUser();
# Extract manually requested namespaces
$search->setFeatureData( 'rewrite', $this->runSuggestion );
$search->setLimitOffset( $this->limit, $this->offset );
$search->setNamespaces( $this->namespaces );
+ $search->setSort( $this->sort );
$search->prefix = $this->mPrefix;
Hooks::run( 'SpecialSearchSetupEngine', [ $this, $this->profile, $search ] );
}
$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
* @param string $text
*
* @throws InvalidArgumentException If the namespace is invalid
- * @return string
+ * @return string Namespace name with underscores (not spaces)
*/
public function getNamespaceName( $namespace, $text ) {
if ( $this->language->needsGenderDistinction() &&
* @return string
*/
public function formatTitle( $namespace, $text, $fragment = '', $interwiki = '' ) {
- if ( $namespace !== 0 && $namespace !== false ) {
- // Try to get a namespace name, but fallback
- // to empty string if it doesn't exist. And
- // assume that ns 0 is the empty string.
+ $out = '';
+ if ( $interwiki !== '' ) {
+ $out = $interwiki . ':';
+ }
+
+ if ( $namespace != 0 ) {
try {
$nsName = $this->getNamespaceName( $namespace, $text );
} catch ( InvalidArgumentException $e ) {
- $nsName = '';
+ // See T165149. Awkward, but better than erroneously linking to the main namespace.
+ $nsName = $this->language->getNsText( NS_SPECIAL ) . ":Badtitle/NS{$namespace}";
}
- $text = $nsName . ':' . $text;
- }
- if ( $fragment !== '' ) {
- $text = $text . '#' . $fragment;
+ $out .= $nsName . ':';
}
+ $out .= $text;
- if ( $interwiki !== '' ) {
- $text = $interwiki . ':' . $text;
+ if ( $fragment !== '' ) {
+ $out .= '#' . $fragment;
}
- $text = str_replace( '_', ' ', $text );
+ $out = str_replace( '_', ' ', $out );
- return $text;
+ return $out;
}
/**
* @return string
*/
public function getPrefixedText( LinkTarget $title ) {
- return $this->formatTitle(
- $title->getNamespace(),
- $title->getText(),
- '',
- $title->getInterwiki()
- );
+ if ( !isset( $title->prefixedText ) ) {
+ $title->prefixedText = $this->formatTitle(
+ $title->getNamespace(),
+ $title->getText(),
+ '',
+ $title->getInterwiki()
+ );
+ }
+
+ return $title->prefixedText;
}
/**
* @return string
*/
public function getPrefixedDBkey( LinkTarget $target ) {
- $key = '';
- if ( $target->isExternal() ) {
- $key .= $target->getInterwiki() . ':';
- }
- // Try to get a namespace name, but fallback
- // to empty string if it doesn't exist
- try {
- $nsName = $this->getNamespaceName(
- $target->getNamespace(),
- $target->getText()
- );
- } catch ( InvalidArgumentException $e ) {
- $nsName = '';
- }
-
- if ( $target->getNamespace() !== 0 ) {
- $key .= $nsName . ':';
- }
-
- $key .= $target->getText();
-
- return strtr( $key, ' ', '_' );
+ return strtr( $this->formatTitle(
+ $target->getNamespace(),
+ $target->getDBkey(),
+ '',
+ $target->getInterwiki()
+ ), ' ', '_' );
}
/**
*/
protected $interwiki;
+ /**
+ * Text form including namespace/interwiki, initialised on demand
+ *
+ * Only public to share cache with TitleFormatter
+ *
+ * @private
+ * @var string
+ */
+ public $prefixedText = null;
+
/**
* Constructs a TitleValue.
*
/**
* Reset internal cache for unit testing
+ * @codeCoverageIgnore
*/
public static function resetCache() {
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
--- /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 );
+ }
+}
$iwResults = [];
foreach ( $resultSets as $resultSet ) {
- $result = $resultSet->next();
- while ( $result ) {
+ foreach ( $resultSet as $result ) {
if ( !$result->isBrokenTitle() ) {
$iwResults[$result->getTitle()->getInterwiki()][] = $result;
}
- $result = $resultSet->next();
}
}
$warningDone = true;
}
$startPos += 2;
- continue;
+ break;
}
// Recursively parse another rule
$inner .= $this->recursiveConvertRule( $text, $variant, $startPos, $depth + 1 );
'9只' => '9隻',
'9余' => '9餘',
'·范' => '·范',
+'’m' => '’m',
+'’re' => '’re',
'’s' => '’s',
+'’t' => '’t',
'、面点' => '、麵點',
'。个中' => '。箇中',
'〇周后' => '〇周後',
'债累累' => '債纍纍',
'傻里傻气' => '傻裡傻氣',
'仅余' => '僅餘',
+'像赞' => '像讚',
'仆人' => '僕人',
'仆使' => '僕使',
'仆仆' => '僕僕',
'劉佳怜' => '劉佳怜',
'刘芸后' => '劉芸后',
'劉芸后' => '劉芸后',
-'力拼' => '力拚',
-'力拼众敌' => '力拼眾敵',
'力争上游' => '力爭上遊',
'功勋' => '功勳',
'加氢精制' => '加氫精制',
'勾干' => '勾幹',
'勾心斗角' => '勾心鬥角',
'勾魂荡魄' => '勾魂蕩魄',
+'包干' => '包幹',
'包括' => '包括',
'包准' => '包準',
'包谷' => '包穀',
'寿面' => '壽麵',
'夏于乔' => '夏于喬',
'夏于喬' => '夏于喬',
+'夏后氏' => '夏后氏',
'夏历' => '夏曆',
'夏历史' => '夏歷史',
'夏游' => '夏遊',
'恋恋不舍' => '戀戀不捨',
'成于思' => '成於思',
'戬谷' => '戩穀',
-'截发' => '截髮',
'战天斗地' => '戰天鬥地',
'战栗' => '戰慄',
'战斗' => '戰鬥',
'打卡钟' => '打卡鐘',
'打吨' => '打吨',
'打干' => '打幹',
-'打拼' => '打拚',
'打断发' => '打斷發',
'打卤' => '打滷',
'打谷' => '打穀',
'拔须' => '拔鬚',
'拗别' => '拗彆',
'拙朴' => '拙樸',
-'拼却' => '拚卻',
-'拼命' => '拚命',
-'拼舍' => '拚捨',
-'拼死' => '拚死',
-'拼生尽死' => '拚生盡死',
-'拼绝' => '拚絕',
-'拼老命' => '拚老命',
-'拼斗' => '拚鬥',
+'拚舍' => '拚捨',
'拜托' => '拜託',
'括发' => '括髮',
'拭干' => '拭乾',
'拮据' => '拮据',
'拳局' => '拳跼',
-'æ\8b¼æ»æ\8b¼æ´»' => 'æ\8b¼æ»æ\8b¼æ´»',
+'æ\8b¼æ\96\97' => 'æ\8b¼é¬¥',
'拾沈' => '拾瀋',
'拿准' => '拿準',
'拿破仑' => '拿破崙',
'摄制' => '攝製',
'支干' => '支幹',
'支配欲' => '支配慾',
+'收回' => '收回',
'收获' => '收穫',
'改制成' => '改制成',
'改征' => '改徵',
'卤鸭' => '滷鴨',
'卤鹅' => '滷鵝',
'卤面' => '滷麵',
-'满拼自尽' => '滿拚自盡',
'满满当当' => '滿滿當當',
'满头洋发' => '滿頭洋髮',
'漂荡' => '漂蕩',
'火并非' => '火並非',
'火并' => '火併',
'火山里' => '火山裡',
-'火拼' => '火拚',
'火折子' => '火摺子',
'火签' => '火籤',
'灰蒙' => '灰濛',
'舌干唇焦' => '舌乾唇焦',
'舍入口' => '舍入口',
'舒卷' => '舒捲',
+'舞台风格' => '舞台風格',
'舞后' => '舞后',
'航海历' => '航海曆',
'航海历史' => '航海歷史',
'蟻后' => '蟻后',
'蚃干' => '蠁幹',
'蛮干' => '蠻幹',
-'血拼' => '血拚',
'血余' => '血餘',
'行事历' => '行事曆',
'行事历史' => '行事歷史',
'鉴赏' => '鑑賞',
'長几' => '長几',
'长几' => '長几',
+'长得丑' => '長得醜',
'长历' => '長曆',
'长历史' => '長歷史',
'长发公主' => '長髮公主',
'嗩' => '唢',
'嗶' => '哔',
'嗹' => '𪡏',
-'嘅' => '慨',
'嘆' => '叹',
'嘍' => '喽',
'嘑' => '呼',
'乾陵' => '乾陵',
'乾隆' => '乾隆',
'乾音' => '乾音',
+'乾顺' => '乾顺',
'乾顧' => '乾顾',
'乾顾' => '乾顾',
'乾風' => '乾风',
'山崎闇斋' => '山崎闇斋',
'山崎闇齋' => '山崎闇斋',
'岳託' => '岳讬',
+'峯會' => '峰会',
+'巔峯' => '巅峰',
'巨著' => '巨著',
'乾乾淨淨' => '干干净净',
'乾乾脆脆' => '干干脆脆',
'承乾' => '承乾',
'拉鍊' => '拉链',
'拙著' => '拙著',
-'拚命' => '拚命',
-'拚搏' => '拚搏',
-'拚死' => '拚死',
+'拚却' => '拚却',
+'拚卻' => '拚却',
+'拚弃' => '拚弃',
+'拚棄' => '拚弃',
+'拚生尽死' => '拚生尽死',
+'拚生盡死' => '拚生尽死',
'拾瀋' => '拾渖',
'挨剋' => '挨剋',
'提昇' => '提升',
'氾濫' => '泛滥',
'洗鍊' => '洗练',
'瀋液' => '渖液',
+'满拚自尽' => '满拚自尽',
+'滿拚自盡' => '满拚自尽',
'薰習' => '熏习',
'薰心' => '熏心',
'薰沐' => '熏沐',
'讎正' => '雠正',
'讎問' => '雠问',
'雪鍊' => '雪链',
+'頂峯' => '顶峰',
'項鍊' => '项链',
'飛昇' => '飞升',
'飭令' => '飭令',
'希拉莉' => '希拉蕊',
'希拉里' => '希拉蕊',
'希特拉' => '希特勒',
+'傷殘奧林匹克' => '帕拉林匹克',
+'残疾人奥林匹克' => '帕拉林匹克',
+'残奥会' => '帕運會',
+'殘奧會' => '帕運會',
'巴尔米拉环礁' => '帕邁拉環礁',
'帕劳' => '帛琉',
'希拉克' => '席哈克',
'劳拉' => '蘿拉',
'荧光' => '螢光',
'荧屏' => '螢屏',
-'屏幕' => '螢幕',
'行人路权' => '行人路權',
'行人路權' => '行人路權',
'流動作業系統' => '行動作業系統',
'馬里共和國' => '馬利共和國',
'马里共和国' => '馬利共和國',
'马拉维' => '馬拉威',
+'馬勒當拿' => '馬拉度納',
+'马拉多纳' => '馬拉度納',
'馬斯特里赫特' => '馬斯垂克',
'马斯特里赫特' => '馬斯垂克',
'马耳他' => '馬爾他',
'伴著者' => '伴著者',
'伴著述' => '伴著述',
'伴著錄' => '伴著錄',
+'服务器' => '伺服器',
'布下了' => '佈下了',
'布下的' => '佈下的',
'布光' => '佈光',
'侵占' => '侵佔',
'促著' => '促着',
'俄占' => '俄佔',
+'奧勒岡' => '俄勒岡',
'保障著' => '保障着',
'保障著作' => '保障著作',
'保障著名' => '保障著名',
'備著者' => '備著者',
'備著述' => '備著述',
'備著錄' => '備著錄',
+'帕拉林匹克' => '傷殘奧林匹克',
+'残疾人奥林匹克' => '傷殘奧林匹克',
'傻里傻气' => '傻裏傻氣',
'雇员' => '僱員',
'雇用' => '僱用',
'历史里' => '歷史裏',
'死里求生' => '死裏求生',
'死里逃生' => '死裏逃生',
+'帕運會' => '殘奧會',
'殺著' => '殺着',
'殺著作' => '殺著作',
'殺著名' => '殺著名',
'蘸著錄' => '蘸著錄',
'蜜里调油' => '蜜裏調油',
'荧屏' => '螢屏',
-'屏幕' => '螢幕',
'人行道' => '行人路',
'行家里手' => '行家裏手',
'首席执行官' => '行政總裁',
'糊口' => '餬口',
'馬里蘭' => '馬利蘭',
'马里兰' => '馬利蘭',
+'馬拉度納' => '馬勒當拿',
+'马拉多纳' => '馬勒當拿',
'马拉特·萨芬' => '馬拉特·沙芬',
'馬斯垂克' => '馬斯特里赫特',
'馬爾地夫' => '馬爾代夫',
'可攜式' => '便携式',
'攜帶型' => '便携式',
'促著' => '促着',
+'奧勒岡' => '俄勒冈',
'保護著' => '保护着',
'保鑣' => '保镖',
'保障著' => '保障着',
'尼日爾' => '尼日尔',
'區域網' => '局域网',
'區域網路' => '局域网络',
-'螢幕' => '屏幕',
'展著' => '展着',
'展著書' => '展著书',
'展著作' => '展著作',
'梵谷' => '梵高',
'欠帳' => '欠账',
'死帳' => '死账',
+'帕運會' => '残奥会',
+'傷殘奧林匹克' => '残疾人奥林匹克',
+'帕拉林匹克' => '残疾人奥林匹克',
'庇里牛斯' => '比利牛斯',
'披索' => '比索',
'畢卡索' => '毕加索',
'營運長,' => '首席运营官,',
'馬爾地夫' => '马尔代夫',
'萌島' => '马恩岛',
+'馬勒當拿' => '马拉多纳',
+'馬拉度納' => '马拉多纳',
'馬拉威' => '马拉维',
'馬斯垂克' => '马斯特里赫特',
'馬爾他' => '马耳他',
"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-delete": "Немагчыма выдаліць файл $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 ужо існуе.",
"backend-fail-store": "Немагчыма захаваць файл $1 у $2.",
"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",
"feed-atom": "অ্যাটম",
"red-link-title": "$1 (পাতার অস্তিত্ব নেই)",
"sort-descending": "উল্টো বর্ণক্রমে সাজান",
- "sort-ascending": "বরà§\8dণানুক্রমে সাজান",
+ "sort-ascending": "à¦\8aরà§\8dদà§\8dধানুক্রমে সাজান",
"nstab-main": "পাতা",
"nstab-user": "ব্যবহারকারীর পাতা",
"nstab-media": "মিডিয়া পাতা",
"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": "Билгалона цӀе",
"logentry-managetags-create": "$1 {{GENDER:$2|Кхоьллина}} билгало «$4»",
"log-name-tag": "Билгалонийн тептар",
"rightsnone": "(яц)",
+ "rightslogentry-temporary-group": "$1 (ханна, $2 кхаччалца)",
"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.",
"edit-error-long": "Chyby:\n\n$1",
"revid": "revize $1",
"pageid": "Stránka s ID $1",
- "interfaceadmin-info": "$1\n\nOprávnění editovat celoprojektové soubory s CSS/JS/JSON bylo nedávno odděleno od práva <code>editinterface</code>. Pokud nerozumíte tomu, proč vidíte tuto chybu, podívejte se na [[mw:MediaWiki_1.32/interface-admin]].",
+ "interfaceadmin-info": "Oprávnění editovat celoprojektové soubory s CSS/JS/JSON bylo nedávno omezeno na členy skupiny [[{{int:grouppage-interface-admin}}|{{int:group-interface-admin}}]]. Pro více informací viz [[m:Creation of separate user group for editing sitewide CSS/JS]].",
"rawhtml-notallowed": "Značky <html> nelze používat mimo běžné stránky.",
"gotointerwiki": "Opustit {{GRAMMAR:4sg|{{SITENAME}}}}",
"gotointerwiki-invalid": "Zadaný název je neplatný.",
"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",
"about": "Ăнлантарни",
"article": "Статья",
"newwindow": "(çĕнĕ чӳречере)",
- "cancel": "Пăрахăçла",
+ "cancel": "Çырмасăр хăвар",
"moredotdotdot": "Малалла…",
"mypage": "Страница",
"mytalk": "Сӳтсе явни",
"yourname": "Усă куракан ят:",
"userlogin-yourname": "Усă куракан ят",
"yourpassword": "Вăрттăн сăмах:",
+ "userlogin-yourpassword": "Пароль",
"yourpasswordagain": "Вăрттăн сăмах тепре çырăр:",
"yourdomainname": "Сирĕн доменă:",
"login": "Кĕрĕр",
"nosuchuser": "$1 ятлă хутшăнакан çук.\nÇырнă ята тепĕр хут тĕрĕслĕр, е аяларах вырнаçнă формăна усă курса çĕнĕ хутшăнакана регистрацилĕр.",
"nosuchusershort": "$1 ятлă хутшăнакан çук. Ятне епле çырнине тĕрĕслĕр.",
"nouserspecified": "Сирĕн хутшăнаканăн ятне каламалла.",
- "wrongpassword": "ÐÑ\81иÑ\80 кÄ\83Ñ\82аÑ\80Ñ\82нÄ\83 вÄ\83Ñ\80Ñ\82Ñ\82Ä\83н Ñ\81Ä\83маÑ\85 Ñ\82Ä\95Ñ\80Ä\95Ñ\81 маÑ\80. УÑ\80Ä\83Ñ\85Ñ\85ине кÄ\83Ñ\82аÑ\80Ñ\82Ä\83р.",
+ "wrongpassword": "ЯÑ\82Ä\83 е паÑ\80олÑ\8cÄ\95 Ñ\82Ä\95Ñ\80Ä\95Ñ\81 маÑ\80.\nТепÄ\95Ñ\80 Ñ\85Ñ\83Ñ\82 кÄ\95Ñ\80Ñ\82Ä\95р.",
"wrongpasswordempty": "Пушă мар пароль çырăр тархасшăн.",
"mailmypassword": "Çĕнĕ вăрттăн сăмаха ярса ил",
"passwordremindertitle": "{{grammar:genitive|{{SITENAME}}}} хутшăнаканăн вăрттăн сăмахне асаилтересси",
"accmailtext": "$1 вăрттăн сăмахне кунта леçрĕмĕр: $2.",
"newarticle": "(Çĕнни)",
"newarticletext": "Ссылка урлă эсир халлĕхе çук статья çине куçрăр.\nÇĕнĕ статьяна кĕртес тесен аяларах вырнаçнă чӳречере текста çырăр.\n(тĕплĕнрех пĕлес тесен [$1 пулăшу страниципе] паллашăр).\nЕнчен те эсир кунта йăнăшпа лекнĕ пулсан — сирĕн браузерăн <strong>Каялла</strong> кнопка çине пусăр.",
+ "userpage-userdoesnotexist-view": "\"$1\" аккаунтне туман.",
"usercsspreview": "'''Ан манăр, эсир сирĕн css файл епле пулассине çеç куратăр, ăна халлĕхе çырса хуман!'''",
"userjspreview": "'''Астăвăр, ку сирĕн javascript-файлăн малтанхи курăмĕ кăна, ăна хальлĕхе çырса хуман!'''",
"updated": "(Çĕнелнĕ)",
"templatesusedsection": "Ку пайĕнче усă куракан {{PLURAL:$1|шаблон|шаблонсем}}:",
"template-protected": "(сыхланă)",
"template-semiprotected": "(пĕр пайне сыхланă)",
+ "content-model-wikitext": "викитекст",
"expensive-parserfunction-category": "Кунта эсир чылай ресурс ыйтакан функцисемпе нумай ĕçлекен страницăсене куратăр",
"post-expand-template-argument-category": "Шаблон аргуменчĕсене сиктерсе хăварнă страницăсем",
"undo-norev": "Ку тӳрлетĕве пăрахăçлама май çук — вăл е пулман та, е ăна кăларса пăрахнă.",
"last": "малт.",
"page_first": "пĕрремĕш",
"page_last": "юлашки",
- "history-fieldset-title": "Ð\98Ñ\81Ñ\82оÑ\80ине пÄ\83Ñ\85",
+ "history-fieldset-title": "УлÄ\83Ñ\88Ä\83нниÑ\81ене Ñ\88Ñ\8bÑ\80а",
"histfirst": "киввисем",
"histlast": "çĕннисем",
"historysize": "({{PLURAL:$1|1 байт|$1 байт}})",
"recentchanges-submit": "Кăтарт",
"rcfilters-legend-heading": "<strong>Кĕскетнисем:</strong>",
"rcfilters-other-review-tools": "Урăх пăхмаллисем",
+ "rcfilters-activefilters-hide": "Кăтартмалла мар",
+ "rcfilters-activefilters-show": "Кăтартмалла",
"rcfilters-show-new-changes": "Çĕнĕ улăшăннисем",
"rcfilters-filterlist-title": "Фильтрсем",
"rcfilters-filter-editsbyself-label": "Хăвăр улăштарнисем",
"notargettitle": "Тĕллевне кăтартман",
"pager-newer-n": "{{PLURAL:$1|çĕнĕреххисене 1|$1 çĕнĕреххисене}}",
"pager-older-n": "{{PLURAL:$1|кивĕреххисене 1|$1 кивĕреххисене}}",
+ "apihelp-no-such-module": "\"$1\" модульĕ тупăнмарĕ.",
"booksources": "Кĕнекесен çăлкуçĕсем",
"booksources-search": "Туп",
- "specialloguserlabel": "Ð¥Ñ\83Ñ\82Ñ\88Ä\83накан:",
+ "specialloguserlabel": "ТÄ\83вакан:",
"log": "Логсем",
"logeventslist-submit": "Кăтарт",
"all-logs-page": "Пĕтĕм логсем",
"unwatch": "ан сăна",
"unwatchthispage": "Сăнама пăрах",
"notanarticle": "Ку статья мар",
+ "watchlist-hide": "Кăтартмалла мар",
"watchlist-submit": "Кăтарт",
"watching": "Сăнамаллисем шутне хушасси…",
"unwatching": "Сăнав ят-йышĕнчен кăларса пăрахасси…",
"mycontris": "Хушни",
"anoncontribs": "Хушни",
"contribsub2": "{{GENDER:$3|$1}} валли ($2)",
+ "contributions-userdoesnotexist": "\"$1\" аккаунтне туман.",
"uctop": "(хальхи)",
"month": "Уйăхран (маларах та):",
"year": "Çултан (маларах та):",
"allmessages-filter-submit": "Куç",
"allmessages-filter-translate": "Куçар",
"thumbnail-more": "Пысăклатмалли",
- "filemissing": "Файл тупăнмарĕ",
+ "filemissing": "Файлĕ тупăнмарĕ",
"thumbnail_error": "Пĕчĕк ӳкерчĕке тăваймарăмăр: $1",
"thumbnail_invalid_params": "Пĕчĕк ӳкерчĕкĕн параметрĕ йăнăш",
"import": "Страницăсене импортласси",
"import-noarticle": "Импортламалли страница çук!",
"importlogpage": "Импорт журналĕ",
"tooltip-pt-userpage": "Сирĕн хутшăнакан страници",
- "tooltip-pt-mytalk": "Сирĕн канашлу страници",
+ "tooltip-pt-mytalk": "{{GENDER:|Сирĕн}} сӳтсе явакан страницу",
"tooltip-pt-anontalk": "IP адресне сӳтсе явни",
"tooltip-pt-preferences": "Сирĕн ĕнерлевсем",
"tooltip-pt-watchlist": "Эсир пăхакан страницисем",
"fileduplicatesearch": "Пĕр пек файлсен шыравĕ",
"fileduplicatesearch-filename": "Файл ячĕ:",
"fileduplicatesearch-submit": "Туп",
+ "fileduplicatesearch-noresults": "\"$1\" ятлă файл тупăнмарĕ.",
"specialpages": "Ятарлă страницăсем",
"specialpages-group-maintenance": "Техника обслуживанийĕн отчечĕсем",
"specialpages-group-other": "Ытти ятарлă страницăсем",
"tag-filter": "[[Special:Tags|Тегсен]] фильтрĕ:",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Тег|Тегсем}}]]: $2)",
"tags-title": "Тегсем",
+ "tags-tag": "Тегĕн ячĕ",
"compare-submit": "Танлаштар",
"htmlform-selectorother-other": "Урăххи",
"htmlform-no": "Çук",
"pagelang-select-lang": "Чĕлхе суйлăр",
"mediastatistics-header-audio": "Аудио",
"mediastatistics-header-video": "Видеосем",
- "special-characters-group-symbols": "Символсем"
+ "special-characters-group-symbols": "Символсем",
+ "authmanager-userdoesnotexist": "\"$1\" аккаунтне туман."
}
"Kenn.jensen",
"Saederup92",
"Fitoschido",
- "Jorn Ari"
+ "Jorn Ari",
+ "Fnielsen"
]
},
"tog-underline": "Understreg henvisninger:",
"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.",
"right-editcontentmodel": "Redigere indholdsmodellen for en side",
"right-editinterface": "Ændre brugergrænsefladens tekster",
"right-editusercss": "Ændre andre brugeres CSS filer",
+ "right-edituserjson": "Redigér andre brugeres JSON-filter",
"right-edituserjs": "Ændre andre brugeres JS filer",
"right-editsitecss": "Rediger CSS for hele siden",
"right-editsitejson": "Rediger JSON for hele siden",
"right-editsitejs": "Rediger JavaScript for hele siden",
"right-editmyusercss": "Redigere dine egne CSS-filer",
+ "right-editmyuserjson": "Redigér dine egne bruger-JSON-filer",
"right-editmyuserjs": "Redigere dine egne JavaScript-filer",
"right-viewmywatchlist": "Se din egen overvågningsliste",
"right-editmywatchlist": "Redigere din egen overvågningsliste. Bemærk nogle handlinger tilføjer sider selv uden denne rettelse.",
"grant-createaccount": "Oprette af konti",
"grant-createeditmovepage": "Oprette, redigere og flytte sider",
"grant-delete": "Slette sider, revisioner og logposter",
- "grant-editinterface": "Redigere MediaWiki-navnerummet og bruger/side JSON",
+ "grant-editinterface": "Redigere MediaWiki-navnerummet og JSON for hele webstedet og brugere",
"grant-editmycssjs": "Redigere din bruger-CSS/JSON/JavaScript",
"grant-editmyoptions": "Redigere dine brugerindstillinger",
"grant-editmywatchlist": "Redigere din overvågningsliste",
"rcfilters-advancedfilters": "Avancerede filtre",
"rcfilters-limit-title": "Antal resultater som skal vises",
"rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|ændring|ændringer}}, $2",
+ "rcfilters-date-popup-title": "Tidsperiode at søge i",
"rcfilters-days-title": "De sidste dage",
"rcfilters-hours-title": "De sidste timer",
"rcfilters-days-show-days": "$1 {{PLURAL:$1|dag|dage}}",
"rcfilters-highlighted-filters-list": "Fremhævede: $1",
"rcfilters-quickfilters": "Gemte filtre",
"rcfilters-quickfilters-placeholder-title": "Ingen filtre gemt endnu",
+ "rcfilters-quickfilters-placeholder-description": "For at gemme filterindstillingerne og genbruge dem senere, klik på bogmærkeikonet i området Aktive Filtre herunder.",
"rcfilters-savedqueries-defaultlabel": "Gemte filtre",
"rcfilters-savedqueries-rename": "Omdøb",
"rcfilters-savedqueries-setdefault": "Vælg som grundindstilling",
"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]]):",
"protectedtitles-submit": "Vis sidetitler",
"listusers": "Brugerliste",
"listusers-editsonly": "Vis kun brugere med redigeringer",
+ "listusers-temporarygroupsonly": "Vis kun brugere i midlertidige brugergrupper",
"listusers-creationsort": "Sorter efter oprettelsesdato",
"listusers-desc": "Sortér i faldende rækkefølge",
"usereditcount": "{{PLURAL:$1|én redigering|$1 redigeringer}}",
"apihelp": "API-hjælp",
"apihelp-no-such-module": "Modul \"$1\" ikke fundet.",
"apisandbox": "API-sandkassen",
+ "apisandbox-jsonly": "JavaScript kræves for at bruge API-sandkassen.",
"apisandbox-api-disabled": "API er deaktiveret på dette websted.",
"apisandbox-intro": "Brug denne side til at eksperimentere med '''MediaWiki web service API'''.\nVi henviser til [https://www.mediawiki.org/wiki/API:Main_page dokumentationen af API] for yderligere oplysninger om brug af API. Eksempel: [https://www.mediawiki.org/wiki/API#A_simple_example få indholdet af en forside]. Vælg en handling at se flere eksempler.\n\nBemærk, at selv om dette er en sandkasse, vil handlinger du udfører på denne side redigere wikien.",
"apisandbox-submit": "Lav forespørgsel",
"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",
"Florian",
"Tiin",
"Rhirsch",
- "Metalhead64"
+ "Metalhead64",
+ "Vogone"
]
},
"tog-hideminor": "Kleine Änderungen in den „Letzten Änderungen“ ausblenden",
"tog-editsectiononrightclick": "Einzelne Abschnitte per Rechtsklick bearbeiten",
"tog-enotifrevealaddr": "Ihre E-Mail-Adresse in Benachrichtigungs-E-Mails anzeigen",
"cancel": "Abbrechen",
+ "search-ignored-headings": " #<!-- diese Zeile nicht verändern --> <pre>\n# Überschriften, die von der Suche ignoriert werden.\n# Diese Änderungen werden wirksam, sobald die Seite mit der Überschrift indexiert wurde.\n# Sie können die Seitenindexierung erzwingen, indem Sie einen Nulledit durchführen.\n# Syntax:\n# * Alles, was einer Raute („#“) bis zum Zeilenende folgt, ist ein Kommentar.\n# * Jede nicht-leere Zeile ist der exakte zu ignorierende Titel.\nEinzelnachweise\nWeblinks\nSiehe auch\n #</pre> <!-- diese Zeile nicht verändern -->",
"view-pool-error": "Entschuldigen Sie bitte, dass die Server im Moment überlastet sind.\nZu viele Benutzer versuchen, diese Seite zu besuchen.\nBitte warten Sie einige Minuten, bevor Sie es noch einmal versuchen.\n\n$1",
"generic-pool-error": "Entschuldigen Sie bitte, dass die Server im Moment überlastet sind.\nZu viele Benutzer wollen diese Ressource ansehen.\nBitte warten Sie einen Moment, bevor Sie sie erneut aufrufen.",
"badaccess-group0": "Sie haben nicht die erforderlichen Benutzerrechte für diese Aktion.",
"youhavenewmessagesmanyusers": "Sie haben $1 von vielen Benutzern ($2).",
"youhavenewmessagesmulti": "Sie haben neue Nachrichten: $1",
"confirmable-confirm": "Sind {{GENDER:$1|Sie}} sicher?",
+ "transaction-duration-limit-exceeded": "Um eine große Verzögerung in der Datenreplikation zu vermeiden, wurde diese Transaktion abgebrochen. Die Schreibdauer ($1) hat die Grenze von $2 Sekunden überschritten. Falls Sie viele Objekte auf einmal ändern, versuchen Sie stattdessen, die Änderungen auf mehrere Operationen aufzuteilen.",
"enterlockreason": "Bitte geben Sie einen Grund ein, warum die Datenbank gesperrt werden soll und eine Abschätzung über die Dauer der Sperrung",
"readonlytext": "Die Datenbank ist vorübergehend für Neueinträge und Änderungen gesperrt. Bitte versuchen Sie es später noch einmal.\n\nGrund der Sperrung: $1",
"missing-article": "Der Text von „$1“ $2 wurde nicht in der Datenbank gefunden.\n\nDie Seite ist möglicherweise gelöscht oder verschoben worden.\n\nFalls dies nicht der Fall ist, haben Sie eventuell einen Fehler in der Software gefunden. Bitte melden Sie dies einem [[Special:ListUsers/sysop|Administrator]] unter Nennung der URL.",
"actionthrottledtext": "Im Rahmen einer Anti-Spam-Maßnahme kann diese Aktion in einem kurzen Zeitabstand nur begrenzt oft ausgeführt werden. Diese Grenze haben Sie überschritten.\nBitte versuchen Sie es in ein paar Minuten erneut.",
- "viewsourcetext": "Sie können den Quelltext dieser Seite betrachten und kopieren:",
- "viewyourtext": "Sie können den Quelltext '''Ihrer Bearbeitung''' dieser Seite betrachten und kopieren:",
+ "viewsourcetext": "Sie können den Quelltext dieser Seite betrachten und kopieren.",
+ "viewyourtext": "Sie können den Quelltext <strong>Ihrer Bearbeitung</strong> dieser Seite betrachten und kopieren.",
"protectedinterface": "Diese Seite enthält Text für die Benutzeroberfläche der Software auf diesem Wiki und ist geschützt, um Missbrauch vorzubeugen.\nNutzen Sie bitte [https://translatewiki.net/ translatewiki.net], das Lokalisierungsprojekt von MediaWiki, um Übersetzungen für alle Wikis hinzuzufügen oder zu ändern.",
"editinginterface": "'''Warnung:''' Diese Seite enthält von der MediaWiki-Software genutzten Text.\nÄnderungen auf dieser Seite wirken sich auf die Benutzeroberfläche dieses Wikis aus.\nNutzen Sie bitte [https://translatewiki.net/ translatewiki.net], das Lokalisierungsprojekt von MediaWiki, um Übersetzungen für alle Wikis hinzuzufügen oder zu ändern.",
"translateinterface": "Um Übersetzungen für alle Wikis hinzuzufügen oder zu ändern, verwenden Sie bitte [//translatewiki.net/ translatewiki.net], das MediaWiki-Lokalisierungsprojekt.",
"namespaceprotected": "Sie haben nicht die erforderliche Berechtigung, um Seiten im Namensraum '''$1''' bearbeiten zu können.",
"customcssprotected": "Sie haben nicht die Berechtigung, diese CSS enthaltende Seite zu bearbeiten, da sie die persönlichen Einstellungen eines anderen Benutzers enthält.",
+ "customjsonprotected": "Sie haben nicht die Berechtigung, diese CSS enthaltende Seite zu bearbeiten, da sie die persönlichen Einstellungen eines anderen Benutzers enthält.",
"customjsprotected": "Sie haben nicht die Berechtigung, diese JavaScript enthaltende Seite zu bearbeiten, da es sich hierbei um die persönlichen Einstellungen eines anderen Benutzers handelt.",
+ "sitejsonprotected": "Sie haben nicht die Berechtigung, diese JSON-Seite zu bearbeiten, da sie alle Besucher betreffen könnte.",
"mycustomcssprotected": "Sie haben keine Berechtigung, diese CSS-Seite zu bearbeiten.",
"mycustomjsprotected": "Sie haben keine Berechtigung, diese JavaScript-Seite zu bearbeiten.",
"myprivateinfoprotected": "Sie haben keine Berechtigung, Ihre privaten Informationen zu bearbeiten.",
"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ś.",
"logentry-delete-delete": "{{GENDER:$2|Ο|Η}} $1 διέγραψε τη σελίδα $3",
"logentry-delete-delete_redir": "{{GENDER:$2|Ο|Η}} $1 διέγραψε την ανακατεύθυνση $3 με αντικατάσταση",
"logentry-delete-restore": "{{GENDER:$1|Ο|Η}} $1 αποκατέστησε τη σελίδα $3 ($4)",
+ "logentry-delete-restore-nocount": "{{GENDER:$2|Ο|Η}} $1 αποκατέστησε τη σελίδα $3",
+ "restore-count-revisions": "{{PLURAL:$1|1 τροποποίηση|$1 τροποποιήσεις}}",
+ "restore-count-files": "{{PLURAL:$1|1 αρχείο|$1 αρχεία}}",
"logentry-delete-event": "{{GENDER:$2|Ο|Η}} $1 άλλαξε την ορατότητα {{PLURAL:$5|ενός καταγραφόμενου συμβάντος|$5 καταγραφόμενων συμβάντων}} στο $3: $4",
"logentry-delete-revision": "{{GENDER:$2|Ο|Η}} $1 άλλαξε την ορατότητα {{PLURAL:$5|μίας αναθεώρησης|$5 αναθεωρήσεων}} στη σελίδα $3: $4",
"logentry-delete-event-legacy": "{{GENDER:$2|Ο|Η}} $1 άλλαξε την ορατότητα των καταγραφόμενων συμβάντων στη σελίδα $3",
"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",
"pagedata-bad-title": "Virheellinen otsikko: $1.",
"unregistered-user-config": "Turvallisuussyistä JavaScript-, CSS- ja JSON-käyttäjäalasivuja ei voi ladata rekisteröimättömiltä käyttäjiltä.",
"passwordpolicies": "Salasanakäytännöt",
- "passwordpolicies-summary": "Tämä on luettelo käytössä olevista salasanakäytännöistä tämän wikin käyttäjäryhmille.",
+ "passwordpolicies-summary": "Tämä on luettelo voimassa olevista salasanakäytännöistä tämän wikin käyttäjäryhmille.",
"passwordpolicies-group": "Ryhmä",
"passwordpolicies-policies": "Käytännöt",
- "passwordpolicies-policy-minimalpasswordlength": "Salasanan on oltava ainakin $1 {{PLURAL:$1|merkki|merkkiä}} pitkä",
+ "passwordpolicies-policy-minimalpasswordlength": "Salasanan tulee olla vähintään {{PLURAL:$1|yhden merkin|$1 merkin}} pituinen",
"passwordpolicies-policy-minimumpasswordlengthtologin": "Salasanassa on oltava vähintään $1 {{PLURAL:$1|merkki|merkkiä}} pystyäksesi kirjautumaan",
- "passwordpolicies-policy-passwordcannotmatchusername": "Salasana ei voi olla sama kuin käyttäjänimi",
- "passwordpolicies-policy-passwordcannotmatchblacklist": "Salasana ei voi vastata mustalla listalla olevia salasanoja",
- "passwordpolicies-policy-maximalpasswordlength": "Salasanan on oltava vähemmän kuin $1 {{PLURAL:$1|merkki|merkkiä}} pitkä",
- "passwordpolicies-policy-passwordcannotbepopular": "Salasana ei voi olla {{PLURAL:$1|suosittu salasana|$1 suositun salasanan listalla}}"
+ "passwordpolicies-policy-passwordcannotmatchusername": "Salasana ei saa olla sama kuin käyttäjänimi",
+ "passwordpolicies-policy-passwordcannotmatchblacklist": "Salasana ei saa olla mustalla listalla",
+ "passwordpolicies-policy-maximalpasswordlength": "Salasanan tulee olla lyhyempi kuin $1 {{PLURAL:$1|merkki|merkkiä}}",
+ "passwordpolicies-policy-passwordcannotbepopular": "Salasana ei saa olla {{PLURAL:$1|suosittu salasana|$1 suosituimman salasanan listalla}}"
}
"subject-preview": "Aperçu du sujet :",
"previewerrortext": "Une erreur s’est produite lors de la tentative de prévisualisation de vos modifications.",
"blockedtitle": "L’utilisateur est bloqué.",
- "blockedtext": "<strong>Votre compte utilisateur ou votre adresse IP a été bloqué.</strong>\n\nLe blocage a été effectué par $1.\nLa raison invoquée est la suivante : <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7.\n\nVous pouvez contacter $1 ou un autre [[{{MediaWiki:Grouppage-sysop}}|administrateur]] pour en discuter.\nVous ne pouvez utiliser la fonction « {{int:emailuser}} » que si une adresse de courriel valide est spécifiée dans vos [[Special:Preferences|préférences]] et que si cette fonctionnalité n’a pas été bloquée.\nVotre adresse IP actuelle est $3 et votre identifiant de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
- "autoblockedtext": "Votre adresse IP a été bloquée automatiquement car elle a été utilisée par un autre utilisateur, lui-même bloqué par $1.\nLa raison invoquée est :\n\n: <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7\n\nVous pouvez contacter $1 ou l’un des autres [[{{MediaWiki:Grouppage-sysop}}|administrateurs]] pour discuter de ce blocage.\n\nNotez que vous ne pourrez utiliser la fonctionnalité « {{int:emailuser}} » que si vous avez une adresse de courriel validée dans vos [[Special:Preferences|préférences]] et que cette fonctionnalité n’a pas été désactivée.\n\nVotre adresse IP actuelle est $3, et le numéro de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
+ "blockedtext": "<strong>Votre compte utilisateur ou votre adresse IP a été bloqué.</strong>\n\nLe blocage a été effectué par $1.\nLa raison invoquée est la suivante : <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7.\n\nVous pouvez contacter $1 ou un autre [[{{MediaWiki:Grouppage-sysop}}|administrateur]] pour en discuter.\nVous ne pouvez utiliser la fonction « {{int:emailuser}} » que si une adresse de courriel valide est spécifiée dans vos [[Special:Preferences|préférences]] et que si cette fonctionnalité ne vous a pas été bloquée.\nVotre adresse IP actuelle est $3 et votre identifiant de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
+ "autoblockedtext": "Votre adresse IP a été bloquée automatiquement car elle a été utilisée par un autre utilisateur, lui-même bloqué par $1.\nLa raison invoquée est :\n\n: <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7\n\nVous pouvez contacter $1 ou l’un des autres [[{{MediaWiki:Grouppage-sysop}}|administrateurs]] pour discuter de ce blocage.\n\nNotez que vous ne pourrez utiliser la fonctionnalité « {{int:emailuser}} » que si vous avez une adresse de courriel validée dans vos [[Special:Preferences|préférences]] et que cette fonctionnalité ne vous a pas été désactivée.\n\nVotre adresse IP actuelle est $3, et le numéro de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
"systemblockedtext": "Votre nom d'utilisateur ou votre adresse IP ont été bloqués automatiquement par MediaWiki.\nLa raison donnée est la suivante:\n\n: <em>$2</em>.\n\n* Le début du blocage: $8\n* Expiration du délai de blocage: $6\n* Elément concerné: $7\n\nVotre adresse IP actuelle est $3.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
"blockednoreason": "aucune raison donnée",
"whitelistedittext": "Vous devez vous $1 pour avoir la permission de modifier le contenu.",
"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 →",
"difference-missing-revision": "{{PLURAL:$2|Una versione|$2 versioni}} di questa differenza ($1) {{PLURAL:$2|non è stata trovata|non sono state trovate}}.\n\nQuesto si verifica solitamente seguendo un collegamento obsoleto di un diff a una pagina cancellata.\nI dettagli possono essere trovati nel [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro delle cancellazioni].",
"searchresults": "Risultati della ricerca",
"search-filter-title-prefix": "Ricerca effettuata solo nelle pagine con titolo che inizia con \"$1\"",
- "search-filter-title-prefix-reset": "cerca in tutte le pagine",
+ "search-filter-title-prefix-reset": "Cerca in tutte le pagine",
"searchresults-title": "Risultati della ricerca di \"$1\"",
"titlematches": "Corrispondenze nel titolo delle pagine",
"textmatches": "Corrispondenze nel testo delle pagine",
"filehist-filesize": "Dimensione del file",
"filehist-comment": "Commento",
"imagelinks": "Utilizzo del file",
- "linkstoimage": "{{PLURAL:$1|La seguente pagina contiene|Le seguenti $1 pagine contengono}} collegamenti a questo file:",
- "linkstoimage-more": "Più di $1 {{PLURAL:$1|pagina punta|pagine puntano}} a questo file.\nDi seguito sono elencate solo {{PLURAL:$1|la prima pagina che punta|le prime $1 pagine che puntano}} a questo file.\nÈ disponibile un [[Special:WhatLinksHere/$2|elenco completo]].",
- "nolinkstoimage": "Nessuna pagina contiene collegamenti al file.",
+ "linkstoimage": "{{PLURAL:$1|La seguente pagina usa|Le seguenti $1 pagine usano}} questo file:",
+ "linkstoimage-more": "Più di $1 {{PLURAL:$1|pagina usa|pagine usano}} questo file.\nDi seguito sono elencate solo {{PLURAL:$1|la prima pagina che usa|le prime $1 pagine che usano}} questo file.\nÈ disponibile un [[Special:WhatLinksHere/$2|elenco completo]].",
+ "nolinkstoimage": "Nessuna pagina utilizza questo file.",
"morelinkstoimage": "Visualizza [[Special:WhatLinksHere/$1|altri collegamenti]] a questo file.",
"linkstoimage-redirect": "$1 (reindirizzamento file) $2",
"duplicatesoffile": "{{PLURAL:$1|Il seguente file è un duplicato|I seguenti $1 file sono duplicati}} di questo file ([[Special:FileDuplicateSearch/$2|ulteriori dettagli]]):",
"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": "利用者の削除された投稿",
"permissionserrors": "Masalah idin",
"permissionserrorstext": "Panjengan ora kagungan idin kanggo nglakoni sing panjenengan gayuh amerga {{PLURAL:$1|alesan|alesan-alesan}} iki:",
"permissionserrorstext-withaction": "Panjenengan ora diidinaké $2 amarga {{PLURAL:$1|alasan|alasan}} ing ngisor iki:",
- "recreate-moveddeleted-warn": "'''Pènget: Panjenengan gawé manèh sawijining kaca sing wis tau dibusak.'''\n\nMangga digagas manèh apa pantes nerusaké nyunting kaca iki.\nIng ngisor iki kapacak log pambusakan lan pamindhahan saka kaca iki:",
+ "recreate-moveddeleted-warn": "<strong>Pélik: Panjenengan nggawé manèh kaca kang tau kabusak.</strong>\n\nPanjenengan kudu nglelimbang apa pantes nerusaké mbesut kaca iki.\nIng isor iki kapacak log pambusak lan pangalih saka kaca iki:",
"moveddeleted-notice": "Kaca iki wis dibusak.\nLog busak, reksa, lan alih bab kacané cumepak ing ngisor minangka rujukan.",
"log-fulllog": "Deleng cathetan wutuh",
"edit-hook-aborted": "Besutan diwurungaké déning cangkolan.\nOra ana katerangané.",
"showingresults": "Ing ngisor iki dituduhaké {{PLURAL:$1|'''1''' kasil|'''$1''' kasil}}, wiwitané saking #<strong>$2</strong>.",
"showingresultsinrange": "Nuduhaké nganti {{PLURAL:$1|<strong>1</strong> kasil|<strong>$1</strong> kasil}} sajeroning penthangan #<strong>$2</strong> tekan #<strong>$3</strong>.",
"search-showingresults": "{{PLURAL:$4|Asil <strong>$1</strong> saka <strong>$3</strong>|Asil <strong>$1 – $2</strong> saka <strong>$3</strong>}}",
- "search-nonefound": "Ora ana kasil sing mathuk karo pitakoné.",
+ "search-nonefound": "Ora ana asil kang mathuk kuwèri.",
"search-nonefound-thiswiki": "Ora ana kasil sing jumbuh karo panjalukan ing situs iki.",
"powersearch-legend": "Panggolèkan sabanjuré (''advance search'')",
"powersearch-ns": "Golèk ing mandala aran:",
"rc-enhanced-hide": "Dhelikaké princèn",
"rc-old-title": "kawitané digawé minangka \"$1\"",
"recentchangeslinked": "Owahan magepokan",
- "recentchangeslinked-feed": "Owah-owahan sing gegayutan",
+ "recentchangeslinked-feed": "Owah-owahan kang magepokan",
"recentchangeslinked-toolbox": "Owahan magepokan",
"recentchangeslinked-title": "Owah-owahan kang magepokan \"$1\"",
"recentchangeslinked-summary": "Iki pratélaning owah-owahan sing mentas digawé tumrap ing kaca-kaca sing nggayut sawijining kaca (utawa kaca-kaca anggotaning sawijining kategori).\nKaca ing [[Special:Watchlist|pawawangané panjenegan]] <strong>dikandeli</strong>.",
"logeventslist-submit": "Tuduhaké",
"all-logs-page": "Kabèh log umum",
"alllogstext": "Pitontonan gabungan log-log sing ana ing {{SITENAME}}.\nPanjenengan bisa nyiyutaké sesawangané kanthi milih sawijining jinis log, jeneng panganggo (sènsitif-case), utawa kaca sing gegayutan (uga sènsitif-case).",
- "logempty": "Ora ditemokaké èntri log sing pas.",
+ "logempty": "Ora tinemu wiji kang cocog ing log",
"log-title-wildcard": "Golèk sesirah sing diwiwiti tulisan iki",
"showhideselectedlogentries": "Owah pakatonané èntri log sing dipilih",
"log-edit-tags": "Besut tag saka isian log sing dipilih",
"pageinfo-robot-noindex": "Ora éntuk",
"pageinfo-watchers": "Cacahing sing ngawasi kaca",
"pageinfo-visiting-watchers": "Cacahé pandeleng kaca sing nekani besutan anyar",
- "pageinfo-few-watchers": "{{PLURAL:$1|Sing niliki|Sing niliki}} kurang saka $1",
+ "pageinfo-few-watchers": "{{PLURAL:$1|Kang ndeleng|Kang ndeleng}} kurang saka $1",
"pageinfo-redirects-name": "Cacahing alihan menyang kaca iki",
"pageinfo-subpages-name": "Cacahing anak kaca saka kaca iki",
"pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|alihan|alihan}}; $3 {{PLURAL:$3|non-alihan|non-alihan}})",
- "pageinfo-firstuser": "Sing nggawé kaca",
+ "pageinfo-firstuser": "Kang nggawé kaca",
"pageinfo-firsttime": "Tanggal panggawéning kaca",
- "pageinfo-lastuser": "Sing mbesut kèri dhéwé",
+ "pageinfo-lastuser": "Kang wekasan mbesut",
"pageinfo-lasttime": "Tanggal besutan kèri dhéwé",
"pageinfo-edits": "Gunggunging besutan",
- "pageinfo-authors": "Gunggunging sing nganggit",
+ "pageinfo-authors": "Gunggung kang nganggit",
"pageinfo-recent-edits": "Cacahé besutan saiki (ing dalem $1 pungkasan)",
"pageinfo-recent-authors": "Cacahing sing nganggit dinané iki",
"pageinfo-magic-words": "{{PLURAL:$1|Tembung|Tembung}} mujarab ($1)",
"file-info": "ukuran barkas: $1, jinis MIME: $2",
"file-info-size": "$1 × $2 piksel, ukuran barkas: $3, jinis MIME: $4",
"file-info-size-pages": "$1 × $2 piksel, gedhéné berkas: $3, jinisé MIME: $4, $5 {{PLURAL:$5|kaca|kaca}}",
- "file-nohires": "Ora ana résolusi sing luwih dhuwur.",
+ "file-nohires": "Ora ana résolusi kang luwih dhuwur.",
"svg-long-desc": "Barkas SVG, nominal $1 × $2 piksel, gedhéning barkas: $3",
"svg-long-desc-animated": "Berkas SVG, nominal $1 × $2 piksel, gedhené berkas: $3",
"svg-long-error": "Berkas SVG ora sah: $1",
"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": "ညနေပုံ",
"fileduplicatesearch-filename": "ဖိုင်အမည်:",
"fileduplicatesearch-submit": "ရှာဖွေရန်",
"specialpages": "အထူး စာမျက်နှာများ",
+ "specialpages-note-top": "မှတ်ချက်",
"specialpages-note-restricted": "* ပုံမှန် အထူးစာမျက်နှာများ။\n* <span class=\"mw-specialpagerestricted\">ကန့်သတ်ထားသော အထူးစာမျက်နှာများ။</span>",
"specialpages-group-maintenance": "ထိန်းသိမ်းမှု အစီရင်ခံချက်များ",
"specialpages-group-other": "အခြားအထူးစာမျက်နှာများ",
"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": "မှတ်တမ်း ဖျက်ပစ်ခြင်း",
"tog-watchlisthideminor": "Annascunne 'e cagnamiente piccerille d' 'a lista 'e cuntrollo mia",
"tog-watchlisthideliu": "Annascunne 'e cagnamiénte 'e l'utente riggistrate 'a l'elenco 'e cuntrollo",
"tog-watchlistreloadautomatically": "Recarreca 'a lista 'e paggene cuntrullate automaticamente quanno nu filtro se fosse cagnato (ce buò 'o JavaScript)",
+ "tog-watchlistunwatchlinks": "Azzecca marcature dirette ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) pe' secutà/nun secutà 'e cagnamente a 'e paggene (ce buò 'o JavaScript pe' puté ausà sta funziunalità).",
"tog-watchlisthideanons": "Annascunne 'e cagnamiente fatte d'anonime 'a l'elenco 'e cuntrollo",
"tog-watchlisthidepatrolled": "Annascunne 'e modifiche cuntrullate 'a l'elenco 'e cuntrollo",
"tog-watchlisthidecategorization": "Annascunne 'a categorizzazione d' 'e paggene",
"customcssprotected": "Nun v'è permesso 'a cagnà sta paggena CSS, pecché cuntene 'e mpustaziune perzunale 'e n'at'utente.",
"customjsonprotected": "Nun v'è permesso 'a cagnà sta paggena JSON, pecché cuntene 'e mpustaziune perzunale 'e n'at'utente.",
"customjsprotected": "Nun v'è permesso 'a cagnà sta paggena JavaScript, pecché cuntene 'e mpustaziune perzunale 'e n'at'utente.",
+ "sitecssprotected": "Nun téne premmesse pe' puté cagnà sta paggena CSS pecché putesse dà prubbleme a 'e vvisite",
+ "sitejsonprotected": "Nun téne premmesse pe' puté cagnà sta paggena JSON pecché putesse dà prubbleme a 'e vvisite",
"mycustomcssprotected": "Nun v'è permesso 'a cagnà sta paggena CSS.",
"mycustomjsonprotected": "Nun v'è permesso 'a cagnà sta paggena JSON.",
"mycustomjsprotected": "Nun v'è licenzia pe cagnà sta paggena JavaScript.",
"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\"",
"filehist-filesize": "Dimenziune d\"o file",
"filehist-comment": "Commento",
"imagelinks": "Jonte ê ffiure",
- "linkstoimage": "{{PLURAL:$1|Sta paggena cullega|$1 'e sti paggene cullegano}} a stu file:",
- "linkstoimage-more": "Cchiù 'e $1 {{PLURAL:$1|paggena cullega|paggene cullegano}} a stu file.<br />\nL'elenco ccà abbascio fà vedé {{PLURAL:$1|'a primma paggena ca cullega|'e primme $1 paggene ca cullegano}} sulamente a stu file.<br />\nNa [[Special:WhatLinksHere/$2|lista completa]] è a disposizione.",
- "nolinkstoimage": "Nisciuna paggena cullega a stu file.",
+ "linkstoimage": "{{PLURAL:$1|Pe sta paggena ce buò|Pe $1 'e sti paggene ce buò}} stu file:",
+ "linkstoimage-more": "Cchiù 'e $1 {{PLURAL:$1|paggena buò|paggene vonno}} stu file.<br />\nL'elenco ccà abbascio fà vedé {{PLURAL:$1|'a primma paggena ca buò|'e primme $1 paggene ca vonno}} sulamente a stu file.<br />\nNa [[Special:WhatLinksHere/$2|lista completa]] è a disposizione.",
+ "nolinkstoimage": "Pe' nisciuna paggena ce buò stu file.",
"morelinkstoimage": "Vide [[Special:WhatLinksHere/$1|cchiù cullegamiente]] a stu file.",
"linkstoimage-redirect": "$1 (redirezionamiente d' 'o file) $2",
"duplicatesoffile": "{{PLURAL:$1|'O file ccà abbascio è nu duplicato|'E $1 file ccà abbascio songo duplicate}} 'e stu file ([[Special:FileDuplicateSearch/$2|cchiù nfurmaziune]]):",
"sp-contributions-blocked-notice-anon": "St'IP è bloccato mò.\nL'urdemo elemento d' 'o riggistro 'e blocche è ripurtato ccà abbascio p'avé nu riferimento:",
"sp-contributions-search": "Ascìa 'e contribbute",
"sp-contributions-username": "Nnerizzo IP o nomme utente",
- "sp-contributions-toponly": "Facenno vedé sulamente 'e contribbute 'e l'urdeme verziune",
- "sp-contributions-newonly": "Facenno vedé sulamente 'e contribbute ca songo criazione 'e paggene",
+ "sp-contributions-toponly": "Sulamente 'e contribbute ca songo ll'urdeme verziune",
+ "sp-contributions-newonly": "Sulamente 'e contribbute ca songo criazione 'e paggene",
"sp-contributions-hideminor": "Annascunne cagnamiénte piccerille",
"sp-contributions-submit": "Truova",
"whatlinkshere": "Paggene ca cullegano a chesta",
"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:",
"watchnologin": "Nie jesteś zalogowany",
"addwatch": "Dodaj do listy obserwowanych",
"addedwatchtext": "Strona „[[:$1|$1]]” wraz ze swoją stroną dyskusji została dodana do Twojej [[Special:Watchlist|listy obserwowanych]].",
- "addedwatchtext-talk": "Strona „[[:$1]]” i strony z nią związane zostały dodane do Twojej [[Special:Watchlist|listy obserwowanych]].",
+ "addedwatchtext-talk": "Strona „[[:$1]]” i strona z nią powiązana zostały dodane do Twojej [[Special:Watchlist|listy obserwowanych]].",
"addedwatchtext-short": "Strona „$1” została dodana do twojej listy obserwowanych.",
"removewatch": "Usuń z listy obserwowanych",
"removedwatchtext": "Strona „[[:$1|$1]]” wraz ze swoją stroną dyskusji została usunięta z Twojej [[Special:Watchlist|listy obserwowanych]].",
- "removedwatchtext-talk": "Strona „[[:$1]]” i strony z nią związane zostały usunięte z Twojej [[Special:Watchlist|listy obserwowanych]].",
+ "removedwatchtext-talk": "Strona „[[:$1]]” i strona z nią powiązana zostały usunięte z Twojej [[Special:Watchlist|listy obserwowanych]].",
"removedwatchtext-short": "Strona „$1” została usunięta z twojej listy obserwowanych.",
"watch": "Obserwuj",
"watchthispage": "Obserwuj",
"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.",
"autocomment-prefix": "{{notranslate}}",
"pipe-separator": "{{optional}}",
- "word-separator": "{{optional}}\nThis is a string which is (usually) put between words of the language. It is used, e.g. when messages are concatenated (appended to each other). Note that you must express a space as html entity &#32; because the editing and updating process strips leading and trailing spaces from messages.\n\nMost languages use a space, but some Asian languages, such as Thai and Chinese, do not.",
+ "word-separator": "{{optional}}\nThis is a string which is (usually) put between words of the language. It is used, e.g. when messages are concatenated (appended to each other). Note that you must express a space as html entity &#32; because the editing and updating process strips leading and trailing spaces from messages.\n\nMost languages use a space, but some Asian languages, such as Thai and Chinese, do not.\n{{Format|plain}}",
"ellipsis": "{{optional}}",
"percent": "{{optional}}",
"parentheses": "{{optional}}",
- "brackets": "{{Optional}}",
+ "brackets": "{{Optional}}\n{{Format|plain}}",
"quotation-marks": "Quotation marks, for quoting, sometimes titles etc., depending on the language.\n\nSee: [[w:Non-English usage of quotation marks|Non-English usage of quotation marks on Wikipedia]].\n\nParameters:\n* $1 - text to be wrapped in quotation marks",
"imgmultipageprev": "{{Identical|Previous page}}",
"imgmultipagenext": "{{Identical|Next page}}",
"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.",
"customcssprotected": "Nu aveți permisiunea de a modifica această pagină CSS, deoarece conține setările personale ale altui utilizator.",
"customjsonprotected": "Nu aveți permisiunea de a modifica această pagină JSON, deoarece conține setările personale ale altui utilizator.",
"customjsprotected": "Nu aveți permisiunea de a modifica această pagină JavaScript, deoarece conține setările personale ale altui utilizator.",
+ "sitecssprotected": "Nu aveți dreptul să editați această pagină CSS deoarece poate afecta toți vizitatorii",
+ "sitejsonprotected": "Nu aveți dreptul să editați această pagină JSON deoarece poate afecta toți vizitatorii",
+ "sitejsprotected": "Nu aveți dreptul să editați această pagină JavaScript deoarece poate afecta toți vizitatorii",
"mycustomcssprotected": "Nu aveți permisiunea să modificați această pagină CSS.",
"mycustomjsonprotected": "Nu aveți permisiunea să modificați această pagină JSON.",
"mycustomjsprotected": "Nu aveți permisiunea să modificați această pagină JavaScript.",
"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": "Не мате дозволїня мінити свої наставлїня.",
"blanknamespace": "(Сүрүн)",
"contributions": "{{GENDER:$1|Кыттааччы}} суруйуута (кылаата)",
"contributions-title": "$1 кыттааччы киллэрбит уларытыылара",
- "mycontris": "Суруйуу тиһигэ",
+ "mycontris": "Суруйуу испииһэгэ",
"anoncontribs": "Суруйуу тиһилигэ",
"contribsub2": "$1 ($2) суруйуута",
"contributions-userdoesnotexist": "Маннык \"$1\" кыттааччы аата бэлиэтэниллибэтэх.",
"sunday": "ⴰⵙⴰⵎⴰⵙ",
"monday": "ⴰⵢⵏⴰⵙ",
"tuesday": "ⴰⵙⵉⵏⴰⵙ",
- "wednesday": "Akras",
- "thursday": "ⴰⴽⵡⴰⵙ",
- "friday": "â´°âµ\99âµ\89âµ\8eⵡⴰâµ\99",
- "saturday": "asidyas",
- "sun": "asamas",
- "mon": "Aynas",
- "tue": "Asinas",
- "wed": "Akras",
- "thu": "Akwas",
- "fri": "Asimwas",
- "sat": "Asidyas",
+ "wednesday": "ⵍⴰⵔⴱⵄ",
+ "thursday": "âµ\8dâµ\85âµ\8eâµ\89ⵙ",
+ "friday": "âµ\8dâµ\8aâ´°âµ\8eâµ\84",
+ "saturday": "ⵙⵙⴱⵜ",
+ "sun": "ⵍⵃⴷⴷ",
+ "mon": "ⵍⵜⵏⵉⵏ",
+ "tue": "ⵟⵟⵍⴰⵜⴰ",
+ "wed": "ⵍⴰⵔⴱⵄ",
+ "thu": "ⵍⵅⵎⵉⵙ",
+ "fri": "ⵍⵊⴰⵎⵄ",
+ "sat": "ⵙⵙⴱⵜ",
"january": "ⵉⵏⵏⴰⵢⵔ",
"february": "ⴼⴱⵔⴰⵢⵔ",
"march": "ⵎⴰⵔⵚ",
"june": "ⵢⵓⵏⵢⵓ",
"july": "ⵢⵓⵍⵢⵓⵣ",
"august": "ⵖⵓⵛⵜ",
- "september": "âµ\9bâµ\93âµ\9câ´°âµ\8fⴱⵉⵔ",
+ "september": "âµ\9bâµ\93âµ\9câ´°âµ\8eⴱⵉⵔ",
"october": "ⴽⵜⵓⴱⵔ",
- "november": "âµ\8fâµ\93ⵡⴰâµ\8fⴱⵉⵔ",
- "december": "â´·âµ\93âµ\8aâ´°âµ\8fⴱⵉⵔ",
+ "november": "âµ\8fâµ\93ⵡⴰâµ\8eⴱⵉⵔ",
+ "december": "â´·âµ\93âµ\8aâ´°âµ\8eⴱⵉⵔ",
"january-gen": "ⵉⵏⵏⴰⵢⵔ",
"february-gen": "ⴼⴱⵔⴰⵢⵔ",
"march-gen": "ⵎⴰⵔⵚ",
"june-gen": "ⵢⵓⵏⵢⵓ",
"july-gen": "ⵢⵓⵍⵢⵓⵣ",
"august-gen": "ⵖⵓⵛⵜ",
- "september-gen": "âµ\9bâµ\93âµ\9câ´°âµ\8fⴱⵉⵔ",
+ "september-gen": "âµ\9bâµ\93âµ\9câ´°âµ\8eⴱⵉⵔ",
"october-gen": "ⴽⵜⵓⴱⵔ",
"november-gen": "ⵏⵓⵡⴰⵎⴱⵉⵔ",
- "december-gen": "â´·âµ\93âµ\8aâ´°âµ\8fⴱⵉⵔ",
+ "december-gen": "â´·âµ\93âµ\8aâ´°âµ\8eⴱⵉⵔ",
"jan": "ⵉⵏⵏ",
"feb": "brayr",
- "mar": "âµ\8eâ´°âµ\94",
+ "mar": "âµ\8eâ´°âµ\95",
"apr": "ⴰⴱⵔ",
"may": "ⵎⴰⵢ",
"jun": "ⵢⵓⵏ",
"june-date": "$1 ⵢⵓⵏⵢⵓ",
"july-date": "$1 ⵢⵓⵍⵢⵓⵣ",
"august-date": "$1 ⵖⵓⵛⵜ",
- "september-date": "$1 âµ\9bâµ\93âµ\9câ´°âµ\8fⴱⵉⵔ",
+ "september-date": "$1 âµ\9bâµ\93âµ\9câ´°âµ\8eⴱⵉⵔ",
"october-date": "$1 ⴽⵜⵓⴱⵔ",
- "november-date": "$1 âµ\8fâµ\93ⵡⴰâµ\8fⴱⵉⵔ",
- "december-date": "$1 â´·âµ\93âµ\8aâ´°âµ\8fⴱⵉⵔ",
+ "november-date": "$1 âµ\8fâµ\93ⵡⴰâµ\8eⴱⵉⵔ",
+ "december-date": "$1 â´·âµ\93âµ\8aâ´°âµ\8eⴱⵉⵔ",
"pagecategories": "{{PLURAL:$1|ⵜⴰⴳⴳⴰⵢⵜ|ⵜⴰⴳⴳⴰⵢⵉⵏ}}",
"category_header": "ⵜⴰⵙⵏⵉⵡⵉⵏ ⵏ ⵜⴰⴳⴳⴰⵢⵜ \"$1\"",
"subcategories": "ⵜⵉⴷⵓⴳⴳⴰⵢⵉⵏ",
"updatedmarker": "Tuybddal z tizrink li iğuran",
"printableversion": "ⴰⵎⴱⵔⵉⵎⵉ ⵜⴰⵙⵏⴰ ⴰⴷ",
"permalink": "Azday Bdda illan",
- "print": "Siggz",
+ "print": "ⴰⵎⴱⵔⵉⵎⵉ",
"edit": "ⵙⵏⴼⵍ",
"create": "ⵙⵏⵓⵍⴼⵓ",
"delete": "ⴽⴽⵙ",
"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",
"nosuchaction": "Nuk ekziston ky veprim",
"nosuchactiontext": "Veprimi i specifikuar nga URL është i pavlefshëm.\nJu mund të keni bërë një gabim në shkrimin e URL-së, ose keni ndjekur një lidhje të pasaktë.\nKjo mund të vijë edhe si rezultat i një gabimi në programin e përdorur nga {{SITENAME}}.",
"nosuchspecialpage": "Nuk ekziston kjo faqe speciale",
- "nospecialpagetext": "<strong>Ju keni kërkuar një faqe speciale të pavlefshme.</strong> \n\n Një listë e faqeve speciale të vlefshme mund të gjendet në [[Special:SpecialPages|{{int: specialpages }}]].",
+ "nospecialpagetext": "<strong>Ju keni kërkuar një faqe speciale të pavlefshme.</strong> \n\nNjë listë e faqeve speciale të vlefshme mund të gjendet në [[Special:SpecialPages|{{int: specialpages }}]].",
"error": "Gabim",
"databaseerror": "Gabim në databazë",
"databaseerror-text": "\nKjo mund të tregojë një gabim në software.",
"Stalker"
]
},
- "tog-underline": "Ð\9fодвлаÑ\87еÑ\9aе веза:",
+ "tog-underline": "Ð\9fодвлаÑ\87еÑ\9aе линкова:",
"tog-hideminor": "Сакриј мање измене са списка скорашњих измена",
"tog-hidepatrolled": "Сакриј патролиране измене са списка скорашњих измена",
"tog-newpageshidepatrolled": "Сакриј патролиране странице са списка нових страница",
"tog-hidecategorization": "Сакриј категоризацију страница",
"tog-extendwatchlist": "Прошири списак надгледања за поглед свих промена, не само скорашњих",
"tog-usenewrc": "Групиши измене по страници у скорашњим изменама и списку надгледања",
- "tog-numberheadings": "Ð\90Ñ\83Ñ\82омаÑ\82Ñ\81ки нÑ\83меÑ\80иÑ\88и поднаÑ\81лове",
- "tog-showtoolbar": "Прикажи траку с алаткама за уређивање",
+ "tog-numberheadings": "Аутоматски нумериши наслове",
+ "tog-showtoolbar": "Прикажи траку са алаткама за уређивање",
"tog-editondblclick": "Уреди странице двоструким кликом",
"tog-editsectiononrightclick": "Омогући уређивање одељака десним кликом на њихове наслове",
"tog-watchcreations": "Додај странице које направим и датотеке које отпремим на мој списак надгледања",
"tog-watchdefault": "Додај странице и датотеке које уредим на мој списак надгледања",
"tog-watchmoves": "Додај странице и датотеке које преместим на мој списак надгледања",
- "tog-watchdeletion": "Ð\94одаÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86е и даÑ\82оÑ\82еке коÑ\98е обришем на мој списак надгледања",
+ "tog-watchdeletion": "Ð\94одаÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86е и даÑ\82оÑ\82еке коÑ\98е избришем на мој списак надгледања",
"tog-watchuploads": "Додај датотеке које отпремим на мој списак надгледања",
"tog-watchrollback": "Додај странице на којима сам извршио враћање измена на мој списак надгледања",
"tog-minordefault": "Означавај све измене као мање",
"tog-enotifwatchlistpages": "Пошаљи ми имејл када се промени страница или датотека са мог списка надгледања",
"tog-enotifusertalkpages": "Пошаљи ми имејл кад се промени моја корисничка страница за разговор",
"tog-enotifminoredits": "Пошаљи ми имејл и код мањих измена страница и датотека",
- "tog-enotifrevealaddr": "Откриј моју имејл адресу у порукама обавештења",
+ "tog-enotifrevealaddr": "Откриј моју имејл-адресу у порукама обавештења",
"tog-shownumberswatching": "Прикажи број корисника који надгледају",
"tog-oldsig": "Ваш постојећи потпис:",
- "tog-fancysig": "Сматрај потпис као викитекст (без самоповезивања)",
+ "tog-fancysig": "Сматрај потпис као викитекст (без аутоматског линка)",
"tog-uselivepreview": "Прикажи претпреглед без поновног учитавања странице",
"tog-forceeditsummary": "Упозори ме када не унесем резиме измене",
"tog-watchlisthideown": "Сакриј моје измене са списка надгледања",
"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": "Сакриј категоризацију страница",
"tog-diffonly": "Не приказуј садржај странице испод разлика",
"tog-showhiddencats": "Прикажи скривене категорије",
"tog-norollbackdiff": "Не приказуј разлику након извршеног враћања",
- "tog-useeditwarning": "Упозори ме када напуштам страницу за уређивање с несачуваним променама",
- "tog-prefershttps": "Увек користи сигурну везу док сам пријављен/а.",
+ "tog-useeditwarning": "Упозори ме када напуштам страницу за уређивање са несачуваним променама",
+ "tog-prefershttps": "Увек коÑ\80иÑ\81Ñ\82и Ñ\81игÑ\83Ñ\80нÑ\83 везÑ\83 док Ñ\81ам пÑ\80иÑ\98авÑ\99ен/на.",
"underline-always": "увек",
"underline-never": "никад",
"underline-default": "према теми или прегледачу",
"listingcontinuesabbrev": "наст.",
"index-category": "Пописане странице",
"noindex-category": "Непописане странице",
- "broken-file-category": "Странице с неисправним везама до датотека",
+ "broken-file-category": "Странице са неисправним линковима до датотека",
"categoryviewer-pagedlinks": "$1 ($2)",
"category-header-numerals": "$1–$2",
"about": "О нама",
"tagline": "Извор: {{SITENAME}}",
"help": "Помоћ",
"search": "Претрага",
- "search-ignored-headings": " #<!-- не меÑ\9aаÑ\98Ñ\82е ниÑ\88Ñ\82а Ñ\83 овом Ñ\80едÑ\83 --> <pre>\n# Ð\9dаÑ\81лови коÑ\98и Ñ\9bе биÑ\82и занемаÑ\80ени пÑ\80и пÑ\80еÑ\82Ñ\80ази.\n# Ð\9fÑ\80омене Ñ\81Ñ\83 видÑ\99иве одмаÑ\85 након Ñ\88Ñ\82о Ñ\81е Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81а наÑ\81ловом попиÑ\88е.\n# Ð\9cожеÑ\82е изнÑ\83диÑ\82и поновно попиÑ\81иваÑ\9aе â\80\9eнÑ\83лÑ\82омâ\80\9d изменом.\n# СинÑ\82акÑ\81а Ñ\98е Ñ\81ледеÑ\9bа:\n# * Сваки Ñ\80ед коÑ\98и запоÑ\87иÑ\9aе знаком â\80\9e#â\80\9d Ñ\98е коменÑ\82аÑ\80.\n# * Сваки не пÑ\80азни Ñ\80ед Ñ\98е Ñ\82аÑ\87ан наÑ\81лов коÑ\98и Ñ\9bе биÑ\82и занемаÑ\80ен, Ñ\81 Ñ\82им да Ñ\81е Ñ\80азликÑ\83Ñ\98Ñ\83 мала и велика Ñ\81лова и Ñ\81ве оÑ\81Ñ\82ало\nРеÑ\84еÑ\80енÑ\86е\nСпоÑ\99аÑ\88Ñ\9aе везе\nТакође погледајте\n #</pre> <!-- не мењајте ништа у овом реду -->",
+ "search-ignored-headings": " #<!-- не меÑ\9aаÑ\98Ñ\82е ниÑ\88Ñ\82а Ñ\83 овом Ñ\80едÑ\83 --> <pre>\n# Ð\9dаÑ\81лови коÑ\98и Ñ\9bе биÑ\82и занемаÑ\80ени пÑ\80и пÑ\80еÑ\82Ñ\80ази.\n# Ð\9fÑ\80омене Ñ\81Ñ\83 видÑ\99иве одмаÑ\85 након Ñ\88Ñ\82о Ñ\81е Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81а наÑ\81ловом попиÑ\88е.\n# Ð\9cожеÑ\82е изнÑ\83диÑ\82и поновно попиÑ\81иваÑ\9aе â\80\9eнÑ\83лÑ\82омâ\80\9d изменом.\n# СинÑ\82акÑ\81а Ñ\98е Ñ\81ледеÑ\9bа:\n# * Сваки Ñ\80ед коÑ\98и запоÑ\87иÑ\9aе знаком â\80\9e#â\80\9d Ñ\98е коменÑ\82аÑ\80.\n# * Сваки не пÑ\80азни Ñ\80ед Ñ\98е Ñ\82аÑ\87ан наÑ\81лов коÑ\98и Ñ\9bе биÑ\82и занемаÑ\80ен, Ñ\81 Ñ\82им да Ñ\81е Ñ\80азликÑ\83Ñ\98Ñ\83 мала и велика Ñ\81лова и Ñ\81ве оÑ\81Ñ\82ало\nРеÑ\84еÑ\80енÑ\86е\nСпоÑ\99аÑ\88Ñ\9aи линкови\nТакође погледајте\n #</pre> <!-- не мењајте ништа у овом реду -->",
"searchbutton": "Претражи",
"go": "Иди",
"searcharticle": "Иди",
"history_small": "историја",
"updatedmarker": "ажурирано од моје последње посете",
"printableversion": "За штампање",
- "permalink": "ТÑ\80аÑ\98на веза",
+ "permalink": "ТÑ\80аÑ\98ни линк",
"print": "Штампај",
"view": "Погледај",
"view-foreign": "Погледај на пројекту $1",
"edit-local": "Уреди локални опис",
"create": "Направи",
"create-local": "Додај локални опис",
- "delete": "Ð\9eбриши",
- "undelete_short": "Ð\92Ñ\80аÑ\82и {{PLURAL:$1|обÑ\80иÑ\81анÑ\83 изменÑ\83|$1 обÑ\80иÑ\81ане измене|$1 обрисаних измена}}",
- "viewdeleted_short": "Ð\9fогледаÑ\98 {{PLURAL:$1|Ñ\98еднÑ\83 обÑ\80иÑ\81анÑ\83 изменÑ\83|$1 обÑ\80иÑ\81ане измене|$1 обрисаних измена}}",
+ "delete": "Ð\98збриши",
+ "undelete_short": "Ð\92Ñ\80аÑ\82и {{PLURAL:$1|избÑ\80иÑ\81анÑ\83 изменÑ\83|$1 избÑ\80иÑ\81ане измене|$1 избрисаних измена}}",
+ "viewdeleted_short": "Ð\9fогледаÑ\98 {{PLURAL:$1|Ñ\98еднÑ\83 избÑ\80иÑ\81анÑ\83 изменÑ\83|$1 избÑ\80иÑ\81ане измене|$1 избрисаних измена}}",
"protect": "Заштити",
"protect_change": "промени",
"unprotect": "Промени заштиту",
"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": "Погледај страницу поруке",
"confirmable-no": "Не",
"thisisdeleted": "Погледај или врати $1?",
"viewdeleted": "Погледај $1?",
- "restorelink": "{{PLURAL:$1|Ñ\98еднÑ\83 обÑ\80иÑ\81анÑ\83 изменÑ\83|$1 обÑ\80иÑ\81ане измене|$1 обрисаних измена}}",
+ "restorelink": "{{PLURAL:$1|Ñ\98еднÑ\83 избÑ\80иÑ\81анÑ\83 изменÑ\83|$1 избÑ\80иÑ\81ане измене|$1 избрисаних измена}}",
"feedlinks": "Фид:",
"feed-invalid": "Неважећи тип пријаве на фид.",
"feed-unavailable": "Фидови синдикације нису доступни",
"nstab-category": "Категорија",
"mainpage-nstab": "Главна страна",
"nosuchaction": "Нема такве радње",
- "nosuchactiontext": "РадÑ\9aа наведена Ñ\83 URL-Ñ\83 ниÑ\98е валидна.\nÐ\9cожда Ñ\81Ñ\82е оÑ\82кÑ\83Ñ\86али погÑ\80еÑ\88ан URL-а или Ñ\81Ñ\82е пÑ\80аÑ\82или покваÑ\80енÑ\83 везÑ\83.\nОво такође може да указује на грешку у софтверу који користи {{SITENAME}}.",
+ "nosuchactiontext": "РадÑ\9aа коÑ\98а Ñ\98е наведена Ñ\83 URL-Ñ\83 ниÑ\98е важеÑ\9bа.\nÐ\9cожда Ñ\81Ñ\82е оÑ\82кÑ\83Ñ\86али погÑ\80еÑ\88ан URL-а или Ñ\81Ñ\82е пÑ\80аÑ\82или покваÑ\80ен линк.\nОво такође може да указује на грешку у софтверу који користи {{SITENAME}}.",
"nosuchspecialpage": "Нема такве посебне странице",
"nospecialpagetext": "<strong>Захтевали сте невалидну посебну страницу.</strong>\n\nСписак валидних посебних страница може да се пронађе на „[[Special:SpecialPages|{{int:specialpages}}]]”.",
"error": "Грешка",
"readonly": "База података је закључана",
"enterlockreason": "Унесите разлог за закључавање, укључујући и време откључавања",
"readonlytext": "База података је тренутно закључана, што значи да је није могуће мењати.\n\nСистемски администратор је навео следеће објашњење: $1",
- "missing-article": "ТекÑ\81Ñ\82 Ñ\81Ñ\82Ñ\80аниÑ\86е под називом â\80\9e$1â\80\9c ($2) ниÑ\98е пÑ\80онаÑ\92ен.\n\nУзÑ\80ок ове гÑ\80еÑ\88ке Ñ\98е обиÑ\87но заÑ\81Ñ\82аÑ\80ела измена или веза до обÑ\80иÑ\81ане Ñ\81Ñ\82Ñ\80аниÑ\86е.\n\nÐ\90ко Ñ\81е не Ñ\80ади о Ñ\82оме, онда Ñ\81Ñ\82е веÑ\80оваÑ\82но пÑ\80онаÑ\88ли гÑ\80еÑ\88кÑ\83 Ñ\83 Ñ\81оÑ\84Ñ\82веÑ\80Ñ\83.\nÐ\9fÑ\80иÑ\98авиÑ\82е Ñ\98е [[Special:ListUsers/sysop|админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80Ñ\83]] Ñ\83з одговаÑ\80аÑ\98Ñ\83Ñ\9bÑ\83 везÑ\83.",
+ "missing-article": "ТекÑ\81Ñ\82 Ñ\81Ñ\82Ñ\80аниÑ\86е под називом â\80\9e$1â\80\9c ($2) ниÑ\98е пÑ\80онаÑ\92ен.\n\nУзÑ\80ок ове гÑ\80еÑ\88ке Ñ\98е обиÑ\87но заÑ\81Ñ\82аÑ\80ела измена или линк до избÑ\80иÑ\81ане Ñ\81Ñ\82Ñ\80аниÑ\86е.\n\nÐ\90ко Ñ\81е не Ñ\80ади о Ñ\82оме, онда Ñ\81Ñ\82е веÑ\80оваÑ\82но пÑ\80онаÑ\88ли гÑ\80еÑ\88кÑ\83 Ñ\83 Ñ\81оÑ\84Ñ\82веÑ\80Ñ\83.\nÐ\9fÑ\80иÑ\98авиÑ\82е Ñ\98е [[Special:ListUsers/sysop|админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80Ñ\83]] Ñ\83з одговаÑ\80аÑ\98Ñ\83Ñ\9bи линк.",
"missingarticle-rev": "(ревизија#: $1)",
"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“.",
- "filedeleteerror": "Ð\9dе могÑ\83 да обришем датотеку „$1“.",
+ "filedeleteerror": "Ð\9dе могÑ\83 да избришем датотеку „$1“.",
"directorycreateerror": "Не могу да направим директоријум „$1“.",
"directoryreadonlyerror": "Директоријум „$1“ је само за читање.",
"directorynotreadableerror": "Директоријум „$1“ није читљив.",
"unexpected": "Неочекивана вредност: „$1“=„$2“.",
"formerror": "Грешка: не могу да пошаљем образац.",
"badarticleerror": "Ова радња се не може извршити на овој страници.",
- "cannotdelete": "Ð\9dе могÑ\83 да обÑ\80иÑ\88ем Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 или даÑ\82оÑ\82екÑ\83 â\80\9e$1â\80\9c.\nÐ\92еÑ\80оваÑ\82но Ñ\98Ñ\83 Ñ\98е неко дÑ\80Ñ\83ги обрисао.",
- "cannotdelete-title": "Ð\9dе могÑ\83 да обришем страницу „$1“",
+ "cannotdelete": "Ð\9dе могÑ\83 да избÑ\80иÑ\88ем Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 или даÑ\82оÑ\82екÑ\83 â\80\9e$1â\80\9c.\nÐ\9cогÑ\83Ñ\9bе Ñ\98е да Ñ\98Ñ\83 Ñ\98е неко веÑ\9b избрисао.",
+ "cannotdelete-title": "Ð\9dе могÑ\83 да избришем страницу „$1“",
"delete-hook-aborted": "Брисање је прекинула кука.\nНије дато никакво образложење.",
"no-null-revision": "Не могу да направим нову ништавну ревизију странице „$1“",
"badtitle": "Лош наслов",
- "badtitletext": "Ð\97аÑ\85Ñ\82евани наслов странице је неважећи, празан или је погрешно повезан међујезички или међувики наслов.\nМожда садржи један или више знакова који се не могу користити у насловима.",
+ "badtitletext": "ТÑ\80ажени наслов странице је неважећи, празан или је погрешно повезан међујезички или међувики наслов.\nМожда садржи један или више знакова који се не могу користити у насловима.",
"title-invalid-empty": "Тражено име странице је празно или садржи само назив именског простора.",
"title-invalid-utf8": "Тражени назив странице садржи неважећи UTF-8 знак.",
- "title-invalid-interwiki": "Тражени наслов странице садржи унутрашњу вики везу која не може бити кориштена у насловима.",
+ "title-invalid-interwiki": "Тражени наслов странице садржи унутрашњи вики линк који не може да се користи у насловима.",
"title-invalid-talk-namespace": "Тражени наслов странице се односи на страницу за разговор која не може постојати.",
"title-invalid-characters": "Тражени наслов има неважеће знакове: „$1“.",
"title-invalid-relative": "Наслов има релативну путању. Релативни наслови страница (./, ../) нису важећи јер ће често бити недоступни у корисничком прегледачу.",
"title-invalid-magic-tilde": "Тражени наслов странице садржи неважећи след магичног знака тилда (<nowiki>~~~</nowiki>).",
"title-invalid-too-long": "Тражени назив странице је предугачак. Не сме бити дужи од $1 {{PLURAL:$1|бајта|бајтова}} у UTF-8 кодирању.",
- "title-invalid-leading-colon": "Ð\97аÑ\85Ñ\82евани наслов странице садржи неважећу двотачку на почетку.",
+ "title-invalid-leading-colon": "ТÑ\80ажени наслов странице садржи неважећу двотачку на почетку.",
"perfcached": "Следећи подаци су кеширани и можда нису ажурирани. У кешу {{PLURAL:$1|је доступан највише један резултат|су доступна највише $1 резултата|је доступно највише $1 резултата}}.",
"perfcachedts": "Следећи подаци су кеширани и последњи пут ажурирани на датум $2 у $3 ч. У кешу {{PLURAL:$4|је доступан највише један резултат|су доступна највише $4 резултата|је доступно највише $4 резултата}}.",
"querypage-no-updates": "Ажурирање ове странице је тренутно онемогућено.\nПодаци који се овде налазе могу бити застарели.",
"titleprotected": "Овај назив је [[User:$1|$1]] заштитио од прављења. Разлог: <em>$2</em>.",
"filereadonlyerror": "Не могу да изменим датотеку „$1“ јер је ризница „$2“ у режиму за читање.\n\nСистемски администратор је навео следеће објашњење: „$3“.",
"invalidtitle": "Неважећи наслов",
- "invalidtitle-knownnamespace": "Ð\9dеиÑ\81пÑ\80аван наÑ\81лов Ñ\81 именским простором „$2“ и текстом „$3“",
- "invalidtitle-unknownnamespace": "Ð\9dеиÑ\81пÑ\80аван наÑ\81лов Ñ\81 именским простором бр. $1 и текстом „$2“",
+ "invalidtitle-knownnamespace": "Ð\9dеважеÑ\9bи наÑ\81лов Ñ\81а именским простором „$2“ и текстом „$3“",
+ "invalidtitle-unknownnamespace": "Ð\9dеважеÑ\9bи наÑ\81лов Ñ\81а непознаÑ\82им именским простором бр. $1 и текстом „$2“",
"exception-nologin": "Нисте пријављени",
"exception-nologin-text": "Пријавите се да бисте приступили овој страници или радњи.",
"exception-nologin-text-manual": "Морате бити $1 да бисте приступили овој страници или радњи.",
- "virus-badscanner": "Ð\9dеиÑ\81пÑ\80авно подеÑ\88аваÑ\9aе: непознати скенер за вирусе: <em>$1</em>",
+ "virus-badscanner": "Ð\9bоÑ\88а конÑ\84игÑ\83Ñ\80аÑ\86иÑ\98а: непознати скенер за вирусе: <em>$1</em>",
"virus-scanfailed": "скенирање није успело (код $1)",
"virus-unknownscanner": "непознати антивирус:",
"logouttext": "<strong>Сада сте одјављени.</strong>\n\nЗапамтите да неке странице могу наставити да се приказују као да сте још увек пријављени, све док не очистите кеш свог прегледача.",
"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": "Дошло је до грешке при потврди идентитета базе података или вам није дозвољено да ажурирате свој спољни налог.",
"userlogin-loggedin": "Већ сте пријављени као {{GENDER:$1|$1}}.\nКористите доњи образац да бисте се пријавили као други корисник.",
"userlogin-reauth": "Морате да се поново пријавите да бисте потврдили да сте {{GENDER:$1|$1}}.",
"userlogin-createanother": "Отвори још један налог",
- "createacct-emailrequired": "Имејл адреса",
- "createacct-emailoptional": "Имејл адреса (опционално)",
- "createacct-email-ph": "Унесите своју имејл адресу",
- "createacct-another-email-ph": "Унесите имејл адресу",
- "createaccountmail": "Користите привремену, случајно створену лозинку и пошаљите на наведену имејл адресу",
+ "createacct-emailrequired": "Имејл-адреса",
+ "createacct-emailoptional": "Имејл-адреса (опционално)",
+ "createacct-email-ph": "Унесите своју имејл-адресу",
+ "createacct-another-email-ph": "Унесите имејл-адресу",
+ "createaccountmail": "Користите привремену, случајну лозинку и пошаљите је на наведену имејл-адресу",
"createaccountmail-help": "Може се користити да се некоме отвори налог без сазнања лозинке.",
"createacct-realname": "Право име (опционално)",
"createacct-reason": "Разлог",
"nocookiesfornew": "Кориснички налог није отворен јер његов извор није потврђен.\nОмогућите колачиће на прегледачу и поново учитајте страницу.",
"nocookiesforlogin": "{{int:nocookieslogin}}",
"createacct-loginerror": "Налог је успешно направљен, али се не можете аутоматски пријавити. Пређите на [[Special:UserLogin|ручно пријављивање]].",
- "noname": "Унели Ñ\81Ñ\82е неиÑ\81пÑ\80авно корисничко име.",
+ "noname": "Ð\9dиÑ\81Ñ\82е навели важеÑ\9bе корисничко име.",
"loginsuccesstitle": "Успешно пријављивање",
"loginsuccess": "<strong>Пријављени сте на {{SITENAME}} као „$1”.</strong>",
"nosuchuser": "Не постоји корисник с именом „$1“.\nКорисничка имена су осетљива на мала и велика слова.\nПроверите да ли сте га добро унели или [[Special:CreateAccount|отворите нови налог]].",
"nosuchusershort": "Корисник с именом „$1“ не постоји.\nПроверите да ли сте правилно написали.",
"nouserspecified": "Морате навести корисничко име.",
"login-userblocked": "{{GENDER:$1|Овај корисник је блокиран|Ова корисница је блокирана|Овај корисник је блокиран}}. Пријава није дозвољена.",
- "wrongpassword": "Унели сте неисправно корисничко име или лозинку. Покушајте поново.",
+ "wrongpassword": "Унели сте неисправно корисничко име или лозинку.\nПокушајте поново.",
"wrongpasswordempty": "Нисте унели лозинку. Покушајте поново.",
"passwordtooshort": "Лозинка мора имати најмање {{PLURAL:$1|један знак|$1 знака|$1 знакова}}.",
"passwordtoolong": "Лозинке не могу бити дуже од {{PLURAL:$1|$1 знака|$1 знакова}}.",
"mailmypassword": "Ресетуј лозинку",
"passwordremindertitle": "{{SITENAME}} — привремена лозинка",
"passwordremindertext": "Неко са IP адресе $1 је затражио нову лозинку на викију {{SITENAME}} ($4).\nСтворена је привремена лозинка за {{GENDER:$2|корисника|корисницу|корисника}} $2 која гласи $3.\nУколико је ово ваш захтев, сада се пријавите и поставите нову лозинку.\nПривремена лозинка истиче за {{PLURAL:$5|један дан|$5 дана}}.\n\nАко је неко други затражио промену лозинке, или сте се сетили ваше лозинке и не желите да је мењате, занемарите ову поруку.",
- "noemail": "Не постоји имејл адреса за {{GENDER:$1|корисника|корисницу}} $1.",
- "noemailcreate": "Ð\9cоÑ\80аÑ\82е да наведеÑ\82е валиднÑ\83 имеÑ\98л адресу.",
- "passwordsent": "Нова лозинка је послата на имејл адресу {{GENDER:$1|корисника|кориснице|корисника}} $1.\nПријавите се пошто је примите.",
+ "noemail": "Не постоји имејл-адреса за {{GENDER:$1|корисника|корисницу}} $1.",
+ "noemailcreate": "Ð\9cоÑ\80аÑ\82е да наведеÑ\82е важеÑ\9bÑ\83 имеÑ\98л-адресу.",
+ "passwordsent": "Нова лозинка је послата на имејл-адресу {{GENDER:$1|корисника|кориснице}} $1.\nПоново се пријавите након што је примите.",
"blocked-mailpassword": "Уређивање са ваше IP адресе је блокирано. Ради спречавања злоупотребе, забрањена је и функција враћања лозинке са ње.",
- "eauthentsent": "Ð\9dа наведенÑ\83 имеÑ\98л адÑ\80еÑ\81Ñ\83 Ñ\98е поÑ\81лаÑ\82 поÑ\82вÑ\80дни код.\nÐ\9fÑ\80е него Ñ\88Ñ\82о поÑ\88аÑ\99емо даÑ\99Ñ\9aе поÑ\80Ñ\83ке, пÑ\80аÑ\82иÑ\82е Ñ\83пÑ\83Ñ\82Ñ\81Ñ\82ва Ñ\81 имеÑ\98ла да биÑ\81Ñ\82е поÑ\82вÑ\80дили да Ñ\81Ñ\82е Ð\92и оÑ\82воÑ\80или налог.",
+ "eauthentsent": "Ð\98меÑ\98л о поÑ\82вÑ\80ди Ñ\98е поÑ\81лаÑ\82 на наведенÑ\83 имеÑ\98л-адÑ\80еÑ\81Ñ\83.\nÐ\9fÑ\80е било коÑ\98иÑ\85 дÑ\80Ñ\83гиÑ\85 Ñ\81лаÑ\9aа имеÑ\98лова на налог, моÑ\80аÑ\9bеÑ\82е пÑ\80аÑ\82иÑ\82и Ñ\83пÑ\83Ñ\82Ñ\81Ñ\82ва Ñ\83 имеÑ\98лÑ\83 да биÑ\81Ñ\82е поÑ\82вÑ\80дили да Ñ\98е налог заиÑ\81Ñ\82а ваÑ\88.",
"throttled-mailpassword": "Порука за промену лозинке је послата у {{PLURAL:$1|1=последњих сат времена|последња $1 сата|последњих $1 сати}}.\nДа бисмо спречили злоупотребу, подсетник шаљемо само једном у року од {{PLURAL:$1|1=сат времена|$1 сата|$1 сати}}.",
"mailerror": "Грешка при слању поруке: $1",
"acct_creation_throttle_hit": "Посетиоци овог викија који користе вашу IP адресу су већ отворили {{PLURAL:$1|1=један налог|$1 налога}} претходни $2, што је највећи дозвољени број у том временском периоду.\nЗбог тога посетиоци с ове IP адресе тренутно не могу отворити више налога.",
- "emailauthenticated": "Ваша имејл адреса је потврђена на дан $2 у $3 ч.",
- "emailnotauthenticated": "Ваша имејл адреса још увек није потврђена.\nИмејл неће бити послат ни у једном од следећих случајева.",
- "noemailprefs": "Наведите имејл адресу у својим подешавањима за рад ових могућности.",
- "emailconfirmlink": "Потврдите своју имејл адресу",
- "invalidemailaddress": "Имејл адреса не може бити прихваћена јер је невалидног облика.\nУнесите исправну адресу или оставите празно поље.",
- "cannotchangeemail": "Ð\9dа овом викиÑ\98Ñ\83 не можеÑ\82е пÑ\80омениÑ\82и имеÑ\98л адÑ\80еÑ\81Ñ\83 налога.",
+ "emailauthenticated": "Ваша имејл-адреса је потврђена на дан $2 у $3 ч.",
+ "emailnotauthenticated": "Ваша имејл-адреса још није потврђена.\nНиједан имејл неће да буде послат ни у једном од следећих случајева.",
+ "noemailprefs": "Наведите имејл-адресу у својим подешавањима за оспособљавање ових могућности.",
+ "emailconfirmlink": "Потврдите своју имејл-адресу",
+ "invalidemailaddress": "Имејл-адреса не може да буде прихваћена јер је у неважећем облику.\nУнесите исправну адресу или оставите празно поље.",
+ "cannotchangeemail": "Ð\98меÑ\98л-адÑ\80еÑ\81е налога не могÑ\83 да Ñ\81е пÑ\80омене на овом викиÑ\98Ñ\83.",
"emaildisabled": "Овај сајт не може да шаље имејлове.",
"accountcreated": "Налог је отворен",
"accountcreatedtext": "Кориснички налог [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|talk]]) је отворен.",
"createaccount-title": "Отварање корисничког налога за {{SITENAME}}",
- "createaccount-text": "Неко је отворио налог с вашом имејл адресом на {{SITENAME}} ($4) под именом $2 и лозинком $3.\nПријавите се и промените своју лозинку.\n\nАко је ово грешка, занемарите ову поруку.",
+ "createaccount-text": "Неко је отворио налог са вашом имејл-адресом на пројекту {{SITENAME}} ($4) под именом „$2“ и са лозинком „$3“.\nОдмах требате да се пријавите и промените своју лозинку.\n\nМожете да занемарите ову поруку, ако је овај налог отворен грешком.",
"login-throttled": "Превише пута сте покушали да се пријавите.\nСачекајте $1 пре него што покушате поново.",
"login-abort-generic": "Неуспешна пријава – прекинуто",
"login-migrated-generic": "Ваш налог је мигриран и ваше корисничко више не постоји на овом викију.",
"loginlanguagelabel": "Језик: $1",
- "suspicious-userlogout": "Ваш захтев за одјаву је одбијен јер је послат од стране неисправног прегледача или посредника.",
+ "suspicious-userlogout": "Ваш захтев за одјаву је одбијен јер изгледа да га је послао покварени прегледач или кеширани посредник.",
"createacct-another-realname-tip": "Право име је опционално.\nАко одаберете да га наведете, биће коришћено за приписивање вашег рада.",
"pt-login": "Пријави ме",
"pt-login-button": "Пријави ме",
"pt-createaccount": "Отвори налог",
"pt-userlogout": "Одјави ме",
"php-mail-error-unknown": "Непозната грешка у функцији PHP mail().",
- "user-mail-no-addy": "Покушали сте да пошаљете имејл без имејл адресе.",
+ "user-mail-no-addy": "Покушали сте да пошаљете имејл без имејл-адресе.",
"user-mail-no-body": "Покушано слање имејла с празним или неразумно кратким садржајем.",
"changepassword": "Промена лозинке",
"resetpass_announce": "Да бисте завршили пријаву, подесите нову лозинку овде.",
"botpasswords-label-create": "Направи",
"botpasswords-label-update": "Ажурирај",
"botpasswords-label-cancel": "Откажи",
- "botpasswords-label-delete": "Ð\9eбриши",
+ "botpasswords-label-delete": "Ð\98збриши",
"botpasswords-label-resetpassword": "Ресетуј лозинку",
"botpasswords-label-grants": "Применљиве дозволе:",
"botpasswords-label-grants-column": "Одобрено",
"botpasswords-bad-appid": "Име бота „$1” није валидно.",
"botpasswords-insert-failed": "Неуспело додавање бота под именом „$1”. Можда је већ додат?",
- "botpasswords-update-failed": "Ð\9dеÑ\83Ñ\81пело ажÑ\83Ñ\80иÑ\80аÑ\9aе боÑ\82а под називом â\80\9e$1â\80\9d. Ð\94а ли Ñ\98е обрисан?",
+ "botpasswords-update-failed": "Ð\9dеÑ\83Ñ\81пело ажÑ\83Ñ\80иÑ\80аÑ\9aе боÑ\82а под називом â\80\9e$1â\80\9d. Ð\94а ниÑ\98е избрисан?",
"botpasswords-created-title": "Направљена лозинка бота",
"botpasswords-created-body": "Лозинка за бота „$1” корисника „$2” је направљена.",
"botpasswords-updated-title": "Лозинка бота промењена",
"botpasswords-updated-body": "Лозинка за бота „$1” корисника „$2” је ажурирана.",
- "botpasswords-deleted-title": "Ð\9eбрисана лозинка бота",
- "botpasswords-deleted-body": "Лозинка за бота „$1” корисника „$2” је обрисана.",
+ "botpasswords-deleted-title": "Ð\98збрисана лозинка бота",
+ "botpasswords-deleted-body": "Лозинка за бота „$1” {{GENDER:$2|корисника|кориснице}} „$2” је избрисана.",
"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": "Откажи",
"passwordreset-emaildisabled": "Имејл је онемогућен на овом викију.",
"passwordreset-username": "Корисничко име:",
"passwordreset-domain": "Домен:",
- "passwordreset-email": "Имејл адреса:",
+ "passwordreset-email": "Имејл-адреса:",
"passwordreset-emailtitle": "Детаљи налога на викију {{SITENAME}}",
- "passwordreset-emailtext-ip": "Ð\9dеко (веÑ\80оваÑ\82но Ð\92и, Ñ\81 IP адÑ\80еÑ\81е $1) заÑ\82Ñ\80ажио Ñ\98е Ñ\80еÑ\81еÑ\82оваÑ\9aе ваÑ\88е \nлозинке за пÑ\80оÑ\98екаÑ\82 {{SITENAME}} ($4). СледеÑ\9bи коÑ\80иÑ\81ниÑ\87ки {{PLURAL:$3|налог Ñ\98е повезан|налози Ñ\81Ñ\83 повезани}} \nÑ\81 овом имеÑ\98л адÑ\80еÑ\81ом:\n\n$2\n\n{{PLURAL:$3|Ð\9eва пÑ\80ивÑ\80емена лозинка|Ð\9eве пÑ\80ивÑ\80емене лозинке}} Ñ\9bе иÑ\81Ñ\82еÑ\9bи за {{PLURAL:$5|Ñ\98едан дан|$5 дана}}.\nТÑ\80ебаÑ\82е да Ñ\81е пÑ\80иÑ\98авиÑ\82е и одабеÑ\80иÑ\82е новÑ\83 лозинкÑ\83 одмаÑ\85. Ако је неко други направио овај \nзахтев или сте се сетили своје првобитне лозинке, а не \nжелите да је промените, можете да занемарите ову поруку и наставите да користите своју стару \nлозинку.",
- "passwordreset-emailtext-user": "{{GENDER:$1|Корисник је затражио|Корисница је затражила}} подсетник о подацима за пријаву на викију {{SITENAME}} ($4).\nСледећи {{PLURAL:$3|кориснички налог је повезан|кориснички налози су повезани}} с овом имејл адресом:\n\n$2\n\n{{PLURAL:$3|Привремена лозинка истиче|Привремене лозинке истичу}} за {{PLURAL:$5|један дан|$5 дана}}.\nПријавите се и изаберите нову лозинку. Ако је неко други захтевао ову радњу или сте се сетили лозинке и не желите да је мењате, занемарите ову поруку.",
+ "passwordreset-emailtext-ip": "Ð\9dеко (веÑ\80оваÑ\82но ви, Ñ\81а IP адÑ\80еÑ\81е $1) заÑ\82Ñ\80ажио Ñ\98е Ñ\80еÑ\81еÑ\82оваÑ\9aе ваÑ\88е \nлозинке за пÑ\80оÑ\98екаÑ\82 {{SITENAME}} ($4). СледеÑ\9bи коÑ\80иÑ\81ниÑ\87ки {{PLURAL:$3|налог Ñ\98е повезан|налози Ñ\81Ñ\83 повезани}} \nÑ\81а овом имеÑ\98л адÑ\80еÑ\81ом:\n\n$2\n\n{{PLURAL:$3|Ð\9eва пÑ\80ивÑ\80емена лозинка|Ð\9eве пÑ\80ивÑ\80емене лозинке}} Ñ\9bе иÑ\81Ñ\82еÑ\9bи за {{PLURAL:$5|Ñ\98едан дан|$5 дана}}.\nÐ\9eдмаÑ\85 Ñ\82Ñ\80ебаÑ\82е да Ñ\81е пÑ\80иÑ\98авиÑ\82е и одабеÑ\80иÑ\82е новÑ\83 лозинкÑ\83. Ако је неко други направио овај \nзахтев или сте се сетили своје првобитне лозинке, а не \nжелите да је промените, можете да занемарите ову поруку и наставите да користите своју стару \nлозинку.",
+ "passwordreset-emailtext-user": "{{GENDER:$1|Корисник је затражио|Корисница је затражила}} подсетник о подацима за пријаву на викију {{SITENAME}} ($4).\nСледећи {{PLURAL:$3|кориснички налог је повезан|кориснички налози су повезани}} са овом имејл-адресом:\n\n$2\n\n{{PLURAL:$3|Привремена лозинка истиче|Привремене лозинке истичу}} за {{PLURAL:$5|један дан|$5 дана}}.\nПријавите се и изаберите нову лозинку. Ако је неко други захтевао ову радњу или сте се сетили лозинке и не желите да је мењате, занемарите ову поруку.",
"passwordreset-emailelement": "Корисничко име: \n$1\n\nПривремена лозинка: \n$2",
- "passwordreset-emailsentemail": "Ð\90ко Ñ\98е ово имеÑ\98л адÑ\80еÑ\81а повезана Ñ\81а ваÑ\88им налогом, подÑ\81еÑ\82ник о лозинÑ\86и Ñ\9bе биÑ\82и поÑ\81лаÑ\82 на имеÑ\98л.",
- "passwordreset-emailsentusername": "Ако сте навели имејл адресу приликом регистрације, биће послат имејл за ресетовање лозинке.",
+ "passwordreset-emailsentemail": "Ð\90ко Ñ\98е ова имеÑ\98л-адÑ\80еÑ\81а повезана Ñ\81а ваÑ\88им налогом, онда Ñ\9bе имеÑ\98л о Ñ\80еÑ\81еÑ\82оваÑ\9aÑ\83 лозинке биÑ\82и поÑ\81лаÑ\82.",
+ "passwordreset-emailsentusername": "Ако постоји имејл-адреса повезана са овим корисничким именом, онда ће имејл о ресетовању лозинке бити послат.",
"passwordreset-nocaller": "Позивалац се мора навести",
"passwordreset-nosuchcaller": "Позивалац не постоји: $1",
"passwordreset-ignored": "Ресетовање лозинке није успело. Можда послужилац није конфигурисан?",
- "passwordreset-invalidemail": "Ð\9dеиÑ\81пÑ\80авна имеÑ\98л адреса",
+ "passwordreset-invalidemail": "Ð\9dеважеÑ\9bа имеÑ\98л-адреса",
"passwordreset-nodata": "Корисничко име и адреса е-поште нису наведени",
- "changeemail": "Промена или уклањање имејл адресе",
- "changeemail-header": "Попуните овај образац да би сте променили вашу имејл адресу. Ако жели да ускратите приступ било којој имејл адреси вашем налогу, оставите празно поље за нову имејл адресу приликом попуњавање обрасца.",
+ "changeemail": "Промена или уклањање имејл-адресе",
+ "changeemail-header": "Попуните овај образац да би сте променили вашу имејл-адресу. Ако бисте желели да уклоните повезаност било које имејл-адресе са вашег налога, оставите празно поље за нову имејл-адресу када шаљете образац.",
"changeemail-no-info": "Морате бити пријављени да бисте приступили овој страници.",
- "changeemail-oldemail": "Актуелна имејл адреса:",
- "changeemail-newemail": "Нова имејл адреса:",
+ "changeemail-oldemail": "Актуелна имејл-адреса:",
+ "changeemail-newemail": "Нова имејл-адреса:",
"changeemail-none": "(ништа)",
"changeemail-password": "Ваша лозинка за пројекат {{SITENAME}}:",
"changeemail-submit": "Промени имејл",
"changeemail-throttled": "Превише пута сте покушали да се пријавите.\nМолимо вас да сачекате $1 пре него што покушате поново.",
- "changeemail-nochange": "Унесите другу имејл адресу.",
+ "changeemail-nochange": "Унесите другу имејл-адресу.",
"resettokens": "Ресетовање токена",
- "resettokens-text": "Можете поново поставити жетоне који ће вам омогућити приступ одређеним приватним подацима повезаним са вашим налогом овде.\n\nТребали бисте то да урадите ако их мимо воље поделите с неким или ако је ваш налог угрожен.",
+ "resettokens-text": "Можете поново поставити жетоне који ће вам омогућити приступ одређеним приватним подацима повезаним са вашим налогом овде.\n\nТребали бисте то да урадите ако их мимо воље поделите са неким или ако је ваш налог угрожен.",
"resettokens-no-tokens": "Нема жетона за ресетовање.",
"resettokens-tokens": "Жетони:",
"resettokens-token-label": "$1 (тренутна вредност: $2)",
"bold_tip": "Подебљан текст",
"italic_sample": "Искошен текст",
"italic_tip": "Искошен текст",
- "link_sample": "Ð\9dаÑ\81лов везе",
- "link_tip": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aа веза",
- "extlink_sample": "http://www.example.com/ наÑ\81лов везе",
- "extlink_tip": "СпоÑ\99аÑ\88Ñ\9aа веза (Ñ\81 префиксом http://)",
+ "link_sample": "Ð\9dаÑ\81лов линка",
+ "link_tip": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aи линк",
+ "extlink_sample": "http://www.example.com/ наÑ\81лов линка",
+ "extlink_tip": "СпоÑ\99аÑ\88Ñ\9aи линк (Ñ\81а префиксом http://)",
"headline_sample": "Текст наслова",
"headline_tip": "Поднаслов (ниво 2)",
"nowiki_sample": "Овде уметните необликован текст",
"image_sample": "Пример.jpg",
"image_tip": "Уграђивање датотеке",
"media_sample": "Пример.ogg",
- "media_tip": "Ð\92еза",
+ "media_tip": "Ð\9bинк до даÑ\82оÑ\82еке",
"sig_tip": "Ваш потпис са временском ознаком",
"hr_tip": "Водоравна линија (користите ретко)",
"summary": "Резиме:",
"minoredit": "Ово је мања измена",
"watchthis": "Надгледај ову страницу",
"savearticle": "Сачувај страницу",
- "savechanges": "СаÑ\87Ñ\83ваÑ\98 измене",
+ "savechanges": "СаÑ\87Ñ\83ваÑ\98 пÑ\80омене",
"publishpage": "Објави страницу",
- "publishchanges": "Ð\9eбÑ\98ави измене",
+ "publishchanges": "Ð\9eбÑ\98ави пÑ\80омене",
"savearticle-start": "Сачувај страницу...",
- "savechanges-start": "СаÑ\87Ñ\83ваÑ\98 измене...",
+ "savechanges-start": "СаÑ\87Ñ\83ваÑ\98 пÑ\80омене...",
"publishpage-start": "Објави страницу...",
- "publishchanges-start": "Ð\9eбÑ\98ави измене...",
+ "publishchanges-start": "Ð\9eбÑ\98ави пÑ\80омене...",
"preview": "Претпреглед",
"showpreview": "Прикажи претпреглед",
"showdiff": "Прикажи промене",
"subject-preview": "Преглед теме:",
"previewerrortext": "Дошло је до грешке при покушају прегледа промена.",
"blockedtitle": "Корисник је блокиран",
- "blockedtext": "<strong>Ð\92аÑ\88е коÑ\80иÑ\81ниÑ\87ко име или IP адÑ\80еÑ\81а Ñ\98е блокиÑ\80ана.</strong>\n\nÐ\91локиÑ\80аÑ\9aе Ñ\98е {{GENDER:$4|извÑ\80Ñ\88ио|извÑ\80Ñ\88ила}} $1.\nРазлог Ñ\98е <em>$2</em>.\n\n* Ð\9fоÑ\87еÑ\82ак блокаде: $8\n* Ð\9aÑ\80аÑ\98 блокаде: $6\n* Ð\91локиÑ\80ани коÑ\80иÑ\81ник: $7\n\nÐ\9cожеÑ\82е да конÑ\82акÑ\82иÑ\80аÑ\82е {{GENDER:$4|коÑ\80иÑ\81ника|коÑ\80иÑ\81ниÑ\86Ñ\83}} $1 или дÑ\80Ñ\83гог [[{{MediaWiki:Grouppage-sysop}}|админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80а]] да биÑ\81Ñ\82е Ñ\80азговаÑ\80али о блокади.\nÐ\9dе можеÑ\82е да коÑ\80иÑ\81Ñ\82иÑ\82е могÑ\83Ñ\9bноÑ\81Ñ\82 â\80\9e{{int:emailuser}}â\80\9d оÑ\81им ако Ñ\81Ñ\82е навели ваÑ\99анÑ\83 имеÑ\98л адÑ\80еÑ\81Ñ\83 Ñ\83 Ñ\81воÑ\98им [[Special:Preferences|подеÑ\88аваÑ\9aима налога]] и ниÑ\81Ñ\82е блокиÑ\80ани од коÑ\80иÑ\88Ñ\9bеÑ\9aа иÑ\81Ñ\82е.\nÐ\92аÑ\88а акÑ\82Ñ\83елна IP адÑ\80еÑ\81а Ñ\98е $3, а ID блокаде #$5.\nУкÑ\99Ñ\83Ñ\87ите све горње детаље при прављењу било каквих упита.",
+ "blockedtext": "<strong>Ð\92аÑ\88е коÑ\80иÑ\81ниÑ\87ко име или IP адÑ\80еÑ\81а Ñ\98е блокиÑ\80ана.</strong>\n\nÐ\91локиÑ\80аÑ\9aе Ñ\98е {{GENDER:$4|извÑ\80Ñ\88ио|извÑ\80Ñ\88ила}} $1.\nРазлог Ñ\98е <em>$2</em>.\n\n* Ð\9fоÑ\87еÑ\82ак блокиÑ\80аÑ\9aа: $8\n* Ð\98Ñ\81Ñ\82ек блокиÑ\80аÑ\9aа: $6\n* Ð\91локиÑ\80ани: $7\n\nÐ\9cожеÑ\82е да конÑ\82акÑ\82иÑ\80аÑ\82е {{GENDER:$4|коÑ\80иÑ\81ника|коÑ\80иÑ\81ниÑ\86Ñ\83}} $1 или дÑ\80Ñ\83гог [[{{MediaWiki:Grouppage-sysop}}|админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80а]] да биÑ\81Ñ\82е Ñ\80азговаÑ\80али о блокиÑ\80аÑ\9aÑ\83.\nÐ\9dе можеÑ\82е да коÑ\80иÑ\81Ñ\82иÑ\82е могÑ\83Ñ\9bноÑ\81Ñ\82 â\80\9e{{int:emailuser}}â\80\9d оÑ\81им ако Ñ\81Ñ\82е навели валиднÑ\83 имеÑ\98л адÑ\80еÑ\81Ñ\83 Ñ\83 Ñ\81воÑ\98им [[Special:Preferences|подеÑ\88аваÑ\9aима налога]] и ниÑ\81Ñ\82е блокиÑ\80ани од коÑ\80иÑ\88Ñ\9bеÑ\9aа иÑ\81Ñ\82е.\nÐ\92аÑ\88а акÑ\82Ñ\83елна IP адÑ\80еÑ\81а Ñ\98е $3, а ID блокаде #$5.\nÐ\9dаведите све горње детаље при прављењу било каквих упита.",
"autoblockedtext": "Ваша IP адреса је аутоматски блокирана јер ју је користио други корисник, кога је {{GENDER:$4|блокирао|блокирала}} $1.\nРазлог:\n\n:<em>$2</em>\n\n* Почетак блокаде: $8\n* Крај блокаде: $6\n* Име корисника: $7\n\nМожете да контактирате {{GENDER:$4|корисника|корисницу}} $1 или другог [[{{MediaWiki:Grouppage-sysop}}|администратора]] да бисте расправљали о блокади.\n\nЗапамтите да не можете да користите могућност „{{int:emailuser}}“ осим ако сте навели ваљану имејл адресу у својим [[Special:Preferences|подешавањима]].\n\nВаша актуелна IP адреса је $3, а ID блокаде $5.\nУкључите све горње детаље при прављењу било каквих упита.",
"blockednoreason": "разлог није наведен",
"whitelistedittext": "За уређивање странице је потребно да будете $1.",
"confirmedittext": "Морате да потврдите своју имејл адресу пре уређивања страница.\nПоставите и потврдите имејл адресу преко [[Special:Preferences|подешавања]].",
"nosuchsectiontitle": "Не могу да пронађем одељак.",
- "nosuchsectiontext": "Ð\9fокÑ\83Ñ\88али Ñ\81Ñ\82е да Ñ\83Ñ\80едиÑ\82е одеÑ\99ак коÑ\98и не поÑ\81Ñ\82оÑ\98и.\nÐ\9cожда Ñ\98е пÑ\80емеÑ\88Ñ\82ен или обрисан док сте прегледали страницу.",
+ "nosuchsectiontext": "Ð\9fокÑ\83Ñ\88али Ñ\81Ñ\82е да Ñ\83Ñ\80едиÑ\82е одеÑ\99ак коÑ\98и не поÑ\81Ñ\82оÑ\98и.\nÐ\9cожда Ñ\98е пÑ\80емеÑ\88Ñ\82ен или избрисан док сте прегледали страницу.",
"loginreqtitle": "Потребна је пријава",
"loginreqlink": "пријављени",
"loginreqpagetext": "Морате бити $1 да бисте видели друге странице.",
"anontalkpagetext": "----\n<em>Ово је страница за разговор с анонимним корисником који још нема налог или га не користи.</em>\nЗбог тога морамо да користимо бројчану IP адресу како бисмо га препознали.\nТакву адресу може делити више корисника.\nАко сте анонимни корисник и мислите да су вам упућене примедбе, [[Special:CreateAccount|отворите налог]] или се [[Special:UserLogin|пријавите]] да бисте избегли будућу забуну с осталим анонимним корисницима.",
"noarticletext": "На овој страници тренутно нема текста.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне извештаје] или [{{fullurl:{{FULLPAGENAME}}|action=edit}} направити ову страницу]</span>.",
"noarticletext-nopermission": "Тренутно нема текста на овој страници.\nМожете да [[Special:Search/{{PAGENAME}}|потражите овај наслов странице]] на другим страницама или <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражите сродне евиденције]</span>, али немате дозволу да направите ову страницу.",
- "missing-revision": "Ревизија бр. $1 на страници под именом „{{FULLPAGENAME}}“ не постоји.\n\nОво се обично дешава када пратите застарелу везу до странице која је обрисана.\nВише информација можете да пронађете у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} евиденцији брисања].",
+ "missing-revision": "Ревизија бр. $1 на страници под именом „{{FULLPAGENAME}}“ не постоји.\n\nОво се обично дешава када пратите застарели линк до странице која је избрисана.\nВише информација можете да пронађете у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} евиденцији брисања].",
"userpage-userdoesnotexist": "Кориснички налог „<nowiki>$1</nowiki>“ није отворен.\nРазмислите да ли заиста желите да направите/уредите ову страницу.",
"userpage-userdoesnotexist-view": "Кориснички налог „$1“ није отворен.",
"blocked-notice-logextract": "Овај корисник је тренутно блокиран.\nПоследњи унос у евиденцији блокирања је наведен испод као референца:",
"previewconflict": "Овај преглед осликава како ће изгледати текст у текстуалном оквиру.",
"session_fail_preview": "Извињавамо се! Нисмо могли да обрадимо вашу измену због губитка података сесије.\n\nМожда сте одјављени. <strong>Проверите да ли сте пријављени и покушајте поново</strong>.\nАко и даље не ради, покушајте да се [[Special:UserLogout|одјавите]] и поново пријавите, те проверите да ли су на вашем претраживачу дозвољени колачићи са овог сајта.",
"session_fail_preview_html": "Нисмо могли да обрадимо вашу измену због губитка података сесије.\n\n<em>Будући да је на овом викију омогућен унос HTML ознака, преглед је сакривен као мера предострожности против напада преко јаваскрипта.</em>\n\n<strong>Ако сте покушали да направите праву измену, покушајте поново.<strong>\nАко и даље не ради, покушајте да се [[Special:UserLogout|одјавите]] и поново пријавите и проверите да ли ваш прегледач дозвољава колачиће са овог сајта.",
- "token_suffix_mismatch": "<strong>Ð\92аÑ\88а измена Ñ\98е одбаÑ\87ена Ñ\98еÑ\80 Ñ\98е ваÑ\88 пÑ\80егледаÑ\87 Ñ\83баÑ\86ио знакове инÑ\82еÑ\80пÑ\83нкÑ\86иÑ\98е Ñ\83 новÑ\87иÑ\9b Ñ\83Ñ\80еÑ\92иваÑ\9aа.</strong>\nТо Ñ\81е понекад догаÑ\92а када Ñ\81е коÑ\80иÑ\81Ñ\82и неиÑ\81пÑ\80аван поÑ\81Ñ\80едник.",
+ "token_suffix_mismatch": "<strong>Ð\92аÑ\88а измена Ñ\98е одбиÑ\98ена Ñ\98еÑ\80 Ñ\98е ваÑ\88 клиÑ\98енÑ\82 Ñ\83баÑ\86ио знакове инÑ\82еÑ\80пÑ\83нкÑ\86иÑ\98е Ñ\83 Ñ\82окен Ñ\83Ñ\80еÑ\92иваÑ\9aа.</strong>\nÐ\98змена Ñ\98е одбиÑ\98ена Ñ\80ади Ñ\81пÑ\80еÑ\87аваÑ\9aа Ñ\83ниÑ\88Ñ\82аваÑ\9aа Ñ\82екÑ\81Ñ\82а Ñ\81Ñ\82Ñ\80аниÑ\86е.\nÐ\9eво Ñ\81е понекад догаÑ\92а када коÑ\80иÑ\81Ñ\82иÑ\82е пÑ\80облемаÑ\82иÑ\87ни анонимни поÑ\81Ñ\80едник коÑ\98и Ñ\98е заÑ\81нован на вебÑ\83.",
"edit_form_incomplete": "<strong>Неки делови обрасца за уређивање нису стигли до сервера. Проверите да ли су ваше измене непромењене и покушајте поново.</strong>",
"editing": "Уређујете $1",
"creating": "Прављење странице $1",
"editingold": "<strong>Упозорење: уређујете застарелу ревизију ове странице.</strong>\nАко је сачувате, све промене направљене од ове ревизије ће бити изгубљене.",
"unicode-support-fail": "Ваш прегледач не подржава Unicode. Он је неопоходан за уређивање страница, па зато не могу сачувати измену.",
"yourdiff": "Разлике",
- "copyrightwarning": "Имајте на уму да се сви доприноси на овом викију сматрају као објављени под лиценцом $2 (више на $1).\nАко не желите да се ваши текстови мењају и размењују без ограничења, онда их не шаљите овде.<br />\nИсто тако обећавате да сте Ви аутор текста, или да сте га умножили с извора који је у јавном власништву.\n<strong>Не шаљите радове заштићене ауторским правима без дозволе!</strong>",
+ "copyrightwarning": "Имајте на уму да се сви доприноси на овом викију сматрају као објављени под лиценцом $2 (више на $1).\nАко не желите да се ваши текстови мењају и размењују без ограничења, онда их не шаљите овде.<br />\nИсто тако обећавате да сте Ви аутор текста, или да сте га умножили са извора који је у јавном власништву.\n<strong>Не шаљите радове заштићене ауторским правима без дозволе!</strong>",
"copyrightwarning2": "Имајте на уму да се сви доприноси на овом викију могу мењати, враћати или брисати од других корисника.\nАко не желите да се ваши текстови слободно мењају и расподељују, не шаљите их овде.<br />\nИсто тако обећавате да сте ви аутор текста, или да сте га умножили с извора који је у јавном власништву (више на $1).\n<strong>Не шаљите радове заштићене ауторским правима без дозволе!</strong>",
"editpage-cannot-use-custom-model": "Модел садржаја ове странице се не може променити.",
"longpageerror": "<strong>Грешка: текст који сте унели је величине {{PLURAL:$1|један килобајт|$1 килобајта}}, што је веће од {{PLURAL:$2|дозвољеног једног килобајта|дозвољена $2 килобајта|дозвољених $2 килобајта}}.</strong>\nСтраница не може бити сачувана.",
"readonlywarning": "<strong>Упозорење: база података је закључана ради одржавања, тако да тренутно нећете моћи да сачувате измене.</strong>\nМожда бисте желели сачувати текст за касније у некој текстуалној датотеци.\n\nСистемски администратор је навео следеће објашњење: $1",
"protectedpagewarning": "<strong>Упозорење: Ова страница је заштићена, тако да само корисници са администраторским овлашћењима могу да је уређују.</strong>\nНајновији унос у евиденцији је наведен испод као референца:",
"semiprotectedpagewarning": "<strong>Напомена:</strong> Ова страница је заштићена, тако да само аутоматски потврђени корисници могу да је уређују.\nНајновији унос у евиденцији је наведен испод као референца:",
- "cascadeprotectedwarning": "<strong>Упозорење:</strong> Ова страница је заштићена, тако да само корисници са [[Special:ListGroupRights|одређеним правима]] могу да је уређују, јер је она укључена у {{PLURAL:$1|следећу страницу која је заштићена|следеће странице које су заштићене}} преносивом заштитом:",
+ "cascadeprotectedwarning": "<strong>Упозорење:</strong> Ова страница је заштићена тако да само корисници са [[Special:ListGroupRights|одређеним правима]] могу да је уређују, јер је укључена у {{PLURAL:$1|следећу страницу која је заштићена|следеће странице које су заштићене}} преносивом заштитом:",
"titleprotectedwarning": "<strong>Упозорење: Ова страница је заштићена, тако да су потребна [[Special:ListGroupRights|посебна права]] да се она направи.</strong>\nНајновији унос у евиденцији је наведен испод као референца:",
"templatesused": "{{PLURAL:$1|Шаблон који се користи|Шаблони који се користе}} на овој страници:",
"templatesusedpreview": "{{PLURAL:$1|Шаблон|Шаблони}} у овом претпрегледу:",
"permissionserrorstext": "Немате дозволу за ову радњу из {{PLURAL:$1|следећег|следећих}} разлога:",
"permissionserrorstext-withaction": "Немате дозволу да $2 из {{PLURAL:$1|следећег|следећих}} разлога:",
"contentmodelediterror": "Не можете уредити ову ревизију јер је њен модел садржаја <code>$1</code>, што се разликује од актуелног модела садржаја странице <code>$2</code>.",
- "recreate-moveddeleted-warn": "<strong>УпозоÑ\80еÑ\9aе: поново пÑ\80авиÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 коÑ\98а Ñ\98е пÑ\80еÑ\82Ñ\85одно обÑ\80иÑ\81ана.</strong>\n\nРазмоÑ\82Ñ\80иÑ\82е да ли Ñ\98е пÑ\80икладно да наÑ\81Ñ\82авиÑ\82е Ñ\81 Ñ\83Ñ\80еÑ\92иваÑ\9aем ове Ñ\81Ñ\82Ñ\80аниÑ\86е.\nÐ\9eвде Ñ\98е наведена евиденÑ\86иÑ\98а бÑ\80иÑ\81аÑ\9aа и пÑ\80емеÑ\88Ñ\82аÑ\9aа Ñ\81 образложењем:",
- "moveddeleted-notice": "Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е обрисана.\nЕвиденција брисања, заштите и премештања странице је наведена испод као референца.",
- "moveddeleted-notice-recent": "Ð\96ао нам Ñ\98е, ова Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е недавно обÑ\80иÑ\81ана (Ñ\83 поÑ\81ледÑ\9aиÑ\85 24 Ñ\81аÑ\82а).\nÐ\95виденÑ\86иÑ\98а Ñ\9aеног бÑ\80иÑ\81аÑ\9aа, заÑ\88Ñ\82иÑ\82е и пÑ\80емеÑ\88Ñ\82аÑ\9aа налази Ñ\81е иÑ\81под:",
+ "recreate-moveddeleted-warn": "<strong>УпозоÑ\80еÑ\9aе: Ð\9fоново пÑ\80авиÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 коÑ\98а Ñ\98е пÑ\80еÑ\82Ñ\85одно избÑ\80иÑ\81ана.</strong>\n\nРазмоÑ\82Ñ\80иÑ\82е да ли Ñ\98е пÑ\80икладно да наÑ\81Ñ\82авиÑ\82е Ñ\81а Ñ\83Ñ\80еÑ\92иваÑ\9aем ове Ñ\81Ñ\82Ñ\80аниÑ\86е.\nÐ\9eвде Ñ\98е наведена евиденÑ\86иÑ\98а бÑ\80иÑ\81аÑ\9aа и пÑ\80емеÑ\88Ñ\82аÑ\9aа Ñ\81а образложењем:",
+ "moveddeleted-notice": "Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е избрисана.\nЕвиденција брисања, заштите и премештања странице је наведена испод као референца.",
+ "moveddeleted-notice-recent": "Ð\9dажалоÑ\81Ñ\82, ова Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е недавно избÑ\80иÑ\81ана (Ñ\83 поÑ\81ледÑ\9aиÑ\85 24 Ñ\81аÑ\82а).\nÐ\95виденÑ\86иÑ\98а бÑ\80иÑ\81аÑ\9aа, заÑ\88Ñ\82иÑ\82е и пÑ\80емеÑ\88Ñ\82аÑ\9aа Ñ\81Ñ\82Ñ\80аниÑ\86е наведена Ñ\98е иÑ\81под као Ñ\80еÑ\84еÑ\80енÑ\86а:",
"log-fulllog": "Погледај целу евиденцију",
"edit-hook-aborted": "Измену је прекинула кука.\nНије дато никакво образложење.",
- "edit-gone-missing": "Ð\9dе могÑ\83 да ажÑ\83Ñ\80иÑ\80ам Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83.\nÐ\98згледа да Ñ\98е обрисана.",
+ "edit-gone-missing": "Ð\9dе могÑ\83 да ажÑ\83Ñ\80иÑ\80ам Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83.\nÐ\98згледа да Ñ\98е избрисана.",
"edit-conflict": "Сукоб измена.",
"edit-no-change": "Ваша измена је занемарена јер није било никаквих промена у тексту.",
"postedit-confirmation-created": "Страница је направљена.",
"edit-already-exists": "Не могу да направим страницу.\nИзгледа да она већ постоји.",
"defaultmessagetext": "Подразумевани текст поруке",
"content-failed-to-parse": "Не могу да рашчланим садржај типа $2 за модел $1: $3",
- "invalid-content-data": "Ð\9dеиÑ\81пÑ\80авни подаци садржаја",
+ "invalid-content-data": "Ð\9dеважеÑ\9bи подаци садржаја",
"content-not-allowed-here": "Садржај модела „$1“ није дозвољен на страници [[$2]]",
"editwarning-warning": "Ако напустите ову страницу, изгубићете све измене које сте направили. Ако сте пријављени, можете онемогућити ово упозорење у својим подешавањима, у одељку „{{int:prefs-editing}}“.",
"editpage-invalidcontentmodel-title": "Модел садржаја није подржан",
"converter-manual-rule-error": "Пронађена је грешка у правилу за ручно претварање језика",
"undo-success": "Измена се може поништити.\nПроверите разлике испод, па сачувајте измене.",
"undo-failure": "Ова измена се не може поништити због сукоба измена.",
- "undo-norev": "Ð\9dе могÑ\83 да вÑ\80аÑ\82им изменÑ\83 Ñ\98еÑ\80 не поÑ\81Ñ\82оÑ\98и или Ñ\98е обрисана.",
+ "undo-norev": "Ð\9dе могÑ\83 да вÑ\80аÑ\82им изменÑ\83 Ñ\98еÑ\80 не поÑ\81Ñ\82оÑ\98и или Ñ\98е избрисана.",
"undo-nochange": "Изгледа да је измена већ поништена.",
"undo-summary": "Поништена ревизија $1 {{GENDER:$2|корисника|кориснице}} [[Special:Contributions/$2|$2]] ([[User talk:$2|разговор]])",
"undo-summary-username-hidden": "Поништи измену $1 скривеног корисника",
"last": "разл",
"page_first": "прва",
"page_last": "последња",
- "histlegend": "Избор разлика: означите кутијице ревизија за упоређивање и притисните enter или дугме на дну.<br />\nОбјашњење: <strong>({{int:cur}})</strong> = разлика с актуелном ревизијом, <strong>({{int:last}})</strong> = разлика с претходном ревизијом, <strong>{{int:minoreditletter}}</strong> = мања измена",
+ "histlegend": "Избор разлика: означите кутијице ревизија за упоређивање и притисните enter или дугме на дну.<br />\nОбјашњење: <strong>({{int:cur}})</strong> = разлика са актуелном ревизијом, <strong>({{int:last}})</strong> = разлика са претходном ревизијом, <strong>{{int:minoreditletter}}</strong> = мања измена",
"history-fieldset-title": "Претрага измена",
- "history-show-deleted": "Само обрисане ревизије",
+ "history-show-deleted": "Само избрисане ревизије",
"histfirst": "најстарије",
"histlast": "најновије",
"historysize": "({{PLURAL:$1|1 бајт|$1 бајта|$1 бајтова}})",
"history-feed-title": "Историја ревизија",
"history-feed-description": "Историја измена ове странице на викију",
"history-feed-item-nocomment": "$1 у $2",
- "history-feed-empty": "ТÑ\80ажена Ñ\81Ñ\82Ñ\80аниÑ\86а не поÑ\81Ñ\82оÑ\98и.\nÐ\9cогÑ\83Ñ\9bе да Ñ\98е обÑ\80иÑ\81ана Ñ\81 викиÑ\98а или Ñ\98е пÑ\80еименована.\nÐ\9fокÑ\83Ñ\88аÑ\98Ñ\82е да [[Special:Search|пÑ\80еÑ\82Ñ\80ажиÑ\82е вики]] за Ñ\81лиÑ\87не странице.",
+ "history-feed-empty": "ТÑ\80ажена Ñ\81Ñ\82Ñ\80аниÑ\86а не поÑ\81Ñ\82оÑ\98и.\nÐ\9cогÑ\83Ñ\9bе да Ñ\98е избÑ\80иÑ\81ана Ñ\81а викиÑ\98а или Ñ\98е пÑ\80еименована.\nÐ\9fокÑ\83Ñ\88аÑ\98Ñ\82е да [[Special:Search|пÑ\80еÑ\82Ñ\80ажиÑ\82е вики]] за Ñ\80елеванÑ\82не нове странице.",
"history-edit-tags": "Уреди ознаке изабраних ревизија",
"rev-deleted-comment": "(опис измене уклоњен)",
"rev-deleted-user": "(корисничко име уклоњено)",
"rev-delundel": "промени видљивост",
"rev-showdeleted": "прикажи",
"revisiondelete": "Брисање/враћање ревизија",
- "revdelete-nooldid-title": "Ð\9dема Ñ\82Ñ\80ажене измене",
+ "revdelete-nooldid-title": "Ð\9dеважеÑ\9bа одÑ\80едиÑ\88на Ñ\80евизиÑ\98а",
"revdelete-nooldid-text": "Нисте навели одредишну ревизију на којој треба да се изврши ова функција, та ревизија не постоји, или покушавате да сакријете актуелну ревизију.",
"revdelete-no-file": "Тражена датотека не постоји.",
- "revdelete-show-file-confirm": "Ð\88еÑ\81Ñ\82е ли Ñ\81игÑ\83Ñ\80ни да желиÑ\82е да видиÑ\82е обрисану ревизију датотеке „<nowiki>$1</nowiki>“ од $2; $3?",
+ "revdelete-show-file-confirm": "Ð\88еÑ\81Ñ\82е ли Ñ\81игÑ\83Ñ\80ни да желиÑ\82е да видиÑ\82е избрисану ревизију датотеке „<nowiki>$1</nowiki>“ од $2; $3?",
"revdelete-show-file-submit": "Да",
"revdelete-selected-text": "{{PLURAL:$1|Изабрана ревизија|Изабране ревизије|Изабраних ревизија}} [[:$2]]:",
"revdelete-selected-file": "{{PLURAL:$1|Изабрана верзија датотеке|Изабране верзије датотеке}} [[:$2]]:",
"logdelete-selected": "{{PLURAL:$1|Изабрана ставка у историји|Изабране ставке у историји}}:",
"revdelete-text-text": "Избрисане ревизије ће и даље бити видљиве у историји странице, али делови њиховог садржаја неће бити јавно доступни.",
"revdelete-text-file": "Избрисане верзије датотеке ће и даље бити видљиве у историји датотеке, али делови њиховог садржаја неће бити јавно доступни.",
- "logdelete-text": "Ð\9eбрисани догађаји у евиденцијама ће се идаље појављивати у евиденцији, али ће делови њиховог садржаја бити недоступни јавности.",
+ "logdelete-text": "Ð\98збрисани догађаји у евиденцијама ће се идаље појављивати у евиденцији, али ће делови њиховог садржаја бити недоступни јавности.",
"revdelete-text-others": "Остали администратори ће и даље моћи да приступе скривеном садржају и врате га, осим ако се поставе додатна ограничења.",
- "revdelete-confirm": "Потврдите да намеравате ово урадити, да разумете последице и да то чините у складу с [[{{MediaWiki:Policy-url}}|правилима]].",
+ "revdelete-confirm": "Потврдите да намеравате ово урадити, да разумете последице и да то чините у складу са [[{{MediaWiki:Policy-url}}|правилима]].",
"revdelete-suppress-text": "Сакривање измена би требало користити <strong>само</strong> у следећим случајевима:\n* злонамерни или погрдни подаци\n* неприкладни лични подаци\n*: <em>кућна адреса и број телефона, број кредитне картице, ЈМБГ итд.</em>",
"revdelete-legend": "Ограничења видљивости",
"revdelete-hide-text": "Текст ревизије",
"logdelete-failure": "'''Не могу да поставим видљивост историје:'''\n$1",
"revdel-restore": "промени видљивост",
"pagehist": "Историја странице",
- "deletedhist": "Ð\9eбрисана историја",
+ "deletedhist": "Ð\98збрисана историја",
"revdelete-hide-current": "Грешка при сакривању ставке од $1, $2: ово је актуелна ревизија.\nНе може да буде сакривена.",
"revdelete-show-no-access": "Грешка при приказивању ставке од $1, $2: означена је као „ограничена“.\nНемате приступ до ње.",
"revdelete-modify-no-access": "Грешка при мењању ставке од $1, $2: означена је као „ограничена“.\nНемате приступ до ње.",
"mergehistory-from": "Изворна страница:",
"mergehistory-into": "Одредишна страница:",
"mergehistory-list": "Спојива историја измена",
- "mergehistory-merge": "СледеÑ\9bе Ñ\80евизиÑ\98е Ñ\81Ñ\82Ñ\80аниÑ\86е [[:$1]] могÑ\83 Ñ\81е Ñ\81поÑ\98иÑ\82и Ñ\81а [[:$2]].\nÐ\9aоÑ\80иÑ\81Ñ\82иÑ\82е дÑ\83гмиÑ\9bе Ñ\83 колони да биÑ\81Ñ\82е Ñ\81поÑ\98или Ñ\80евизиÑ\98е коÑ\98е Ñ\81Ñ\83 напÑ\80авÑ\99ене пÑ\80е наведеног вÑ\80емена.\nÐ\9aоÑ\80иÑ\88Ñ\9bеÑ\9aе навигаÑ\86иониÑ\85 Ð²ÐµÐ·а ће поништити ову колону.",
+ "mergehistory-merge": "СледеÑ\9bе Ñ\80евизиÑ\98е Ñ\81Ñ\82Ñ\80аниÑ\86е [[:$1]] могÑ\83 Ñ\81е Ñ\81поÑ\98иÑ\82и Ñ\81а [[:$2]].\nÐ\9aоÑ\80иÑ\81Ñ\82иÑ\82е дÑ\83гмиÑ\9bе Ñ\83 колони да биÑ\81Ñ\82е Ñ\81поÑ\98или Ñ\80евизиÑ\98е коÑ\98е Ñ\81Ñ\83 напÑ\80авÑ\99ене пÑ\80е наведеног вÑ\80емена.\nÐ\9aоÑ\80иÑ\88Ñ\9bеÑ\9aе навигаÑ\86иониÑ\85 Ð»Ð¸Ð½ÐºÐ¾Ð²а ће поништити ову колону.",
"mergehistory-go": "Прикажи измене које се могу спојити",
"mergehistory-submit": "Споји ревизије",
"mergehistory-empty": "Нема измена за спајање.",
"mergehistory-done": "$3 {{PLURAL:$3|ревизија странице $1 је спојена|ревизије странице $1 су спојене|ревизија странице $1 је спојено}} у [[:$2]].",
"mergehistory-fail": "Не могу да спојим историје. Проверите страницу и временске параметре.",
- "mergehistory-fail-bad-timestamp": "Временска ознака није валидна.",
+ "mergehistory-fail-bad-timestamp": "Временска ознака је неважећа.",
"mergehistory-fail-invalid-source": "Изворна страница није валидна.",
- "mergehistory-fail-invalid-dest": "Одредишна страница није валидна.",
+ "mergehistory-fail-invalid-dest": "Одредишна страница је неважећа.",
"mergehistory-fail-no-change": "Спајање историје није спојило ниједну ревизију. Проверите параметре странице и времена.",
"mergehistory-fail-permission": "Немате овлашћење за спајање историје.",
"mergehistory-fail-self-merge": "Изворна и одредишна страница не могу бити исте.",
"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о.",
- "difference-missing-revision": "{{PLURAL:$2|Ð\88една Ñ\80евизиÑ\98а|$2 Ñ\80евизиÑ\98е}} од ове Ñ\80азлике ($1) не {{PLURAL:$2|поÑ\81Ñ\82оÑ\98и|поÑ\81Ñ\82оÑ\98е}}.\n\nÐ\9eво Ñ\81е обиÑ\87но деÑ\88ава када пÑ\80аÑ\82иÑ\82е заÑ\81Ñ\82аÑ\80елÑ\83 везÑ\83 до Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\98а Ñ\98е обрисана.\nДетаље можете да пронађете у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} евиденцији брисања].",
+ "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|Ð\88една Ñ\80евизиÑ\98а|$2 Ñ\80евизиÑ\98е}} ове Ñ\80азлике ($1) не {{PLURAL:$2|поÑ\81Ñ\82оÑ\98и|поÑ\81Ñ\82оÑ\98е}}.\n\nÐ\9eво Ñ\81е обиÑ\87но деÑ\88ава када пÑ\80аÑ\82иÑ\82е заÑ\81Ñ\82аÑ\80ели линк до Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\98а Ñ\98е избрисана.\nДетаље можете да пронађете у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} евиденцији брисања].",
"searchresults": "Резултати претраге",
"search-filter-title-prefix-reset": "Претражи све странице",
"searchresults-title": "Резултати претраге за „$1“",
"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-changeemail": "промени или уклони имејл-адресу",
+ "prefs-setemail": "постави имејл-адресу",
"prefs-email": "Опције имејла",
"prefs-rendering": "Изглед",
"saveprefs": "Сачувај",
"restoreprefs": "Врати сва подешавања на подразумеване вредности (у свим одељцима)",
"prefs-editing": "Уређивање",
"searchresultshead": "Претрага",
- "stub-threshold": "Ð\9fÑ\80аг за обликоваÑ\9aе везе као клице ($1):",
+ "stub-threshold": "Ð\9fÑ\80аг за обликоваÑ\9aе линкова као клице ($1):",
"stub-threshold-sample-link": "пример",
"stub-threshold-disabled": "онемогућено",
"recentchangesdays": "Број дана у скорашњим изменама:",
"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": "Потврда имејла:",
"email": "Имејл",
"prefs-help-realname": "Право име је опционално.\nАко је наведено, биће коришћено за приписивање вашег рада.",
"prefs-help-email": "Имејл адреса је опционална, али је потребна за ресетовање лозинке, ако је заборавите.",
- "prefs-help-email-others": "ТакоÑ\92е можеÑ\82е изабÑ\80аÑ\82и да допÑ\83Ñ\81Ñ\82иÑ\82е дÑ\80Ñ\83гима да ваÑ\81 конÑ\82акÑ\82иÑ\80аÑ\98Ñ\83 пÑ\80еко имеÑ\98ла пÑ\83Ñ\82ем везе на вашој корисничкој страници или страници за разговор.\nВаша имејл адреса неће бити приказана другим корисницима који вас контактирају.",
- "prefs-help-email-required": "Ð\9fоÑ\82Ñ\80ебна Ñ\98е имеÑ\98л адÑ\80еÑ\81а.",
+ "prefs-help-email-others": "ТакоÑ\92е можеÑ\82е изабÑ\80аÑ\82и да допÑ\83Ñ\81Ñ\82иÑ\82е дÑ\80Ñ\83гима да ваÑ\81 конÑ\82акÑ\82иÑ\80аÑ\98Ñ\83 пÑ\80еко имеÑ\98ла пÑ\83Ñ\82ем линка на вашој корисничкој страници или страници за разговор.\nВаша имејл адреса неће бити приказана другим корисницима који вас контактирају.",
+ "prefs-help-email-required": "Ð\98меÑ\98л-адÑ\80еÑ\81а Ñ\98е неопÑ\85одна.",
"prefs-info": "Основне информације",
"prefs-i18n": "Интернационализација",
"prefs-signature": "Потпис",
"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|Имплицитан члан|Имплицитна чланица}} група:",
"userrights-expiry-existing": "Постојеће време истека: $3, $2",
"userrights-expiry-othertime": "Друго време:",
"userrights-expiry-options": "1 дан:1 day,1 недеља:1 week,1 месец:1 month,3 месеца:3 months,6 месеци:6 months,1 година:1 year",
- "userrights-invalid-expiry": "Време истицања групе „$1“ није валидно.",
+ "userrights-invalid-expiry": "Време истицања групе „$1“ је неважеће.",
"userrights-expiry-in-past": "Време истицања групе „$1“ је прошло.",
"userrights-cannot-shorten-expiry": "Не можете убрзати истек чланства у групи „$1”. Само корисници са дозволом да додају или уклоне ову групу могу да убрзају рок истека.",
"userrights-conflict": "Сукоб промена корисничких права! Прегледајте и проверите ваше промене.",
"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-autocreateaccount": "Пријавите се аутоматски са екстерним корисничким налогом",
"right-minoredit": "означавање измена мањим",
"right-move": "премештање страница",
- "right-move-subpages": "премештање страница с њиховим подстраницама",
+ "right-move-subpages": "премештање страница са њиховим подстраницама",
"right-move-rootuserpages": "премештање основних корисничких страница",
"right-move-categorypages": "премештање категорија",
"right-movefile": "премештање датотека",
"right-apihighlimits": "коришћење виших граница за упите из API-ја",
"right-writeapi": "коришћење API-ја за писање",
"right-delete": "брисање страница",
- "right-bigdelete": "брисање страница с великом историјом",
+ "right-bigdelete": "брисање страница са великом историјом",
"right-deletelogentry": "брисање и враћање одређених уноса у евиденцији",
"right-deleterevision": "брисање и враћање одређених ревизија страница",
- "right-deletedhistory": "пÑ\80егледаÑ\9aе обрисаних ставки историје без повезаног текста",
- "right-deletedtext": "пÑ\80егледаÑ\9aе обÑ\80иÑ\81аног Ñ\82екÑ\81Ñ\82а и пÑ\80омена измеÑ\92Ñ\83 обрисаних ревизија",
- "right-browsearchive": "пÑ\80еÑ\82Ñ\80ага обрисаних страница",
- "right-undelete": "вÑ\80аÑ\9bаÑ\9aе обрисаних страница",
+ "right-deletedhistory": "пÑ\80егледаÑ\9aе избрисаних ставки историје без повезаног текста",
+ "right-deletedtext": "пÑ\80егледаÑ\9aе избÑ\80иÑ\81аног Ñ\82екÑ\81Ñ\82а и пÑ\80омена измеÑ\92Ñ\83 избрисаних ревизија",
+ "right-browsearchive": "пÑ\80еÑ\82Ñ\80ага избрисаних страница",
+ "right-undelete": "вÑ\80аÑ\9bаÑ\9aе избрисаних страница",
"right-suppressrevision": "прегледање, скривање и враћање одређених ревизија страница од свих корисника",
"right-viewsuppressed": "прегледање измена скривених од свих корисника",
"right-suppressionlog": "прегледање приватних евиденција",
"right-block": "блокирање даљих измена других корисника",
"right-blockemail": "блокирање корисника да шаљу имејл",
"right-hideuser": "блокирање корисничког имена и његово сакривање од јавности",
- "right-ipblock-exempt": "заобилажење IP блокада, самоблокада и блокада опсега",
+ "right-ipblock-exempt": "заобилажење IP блокада, аутоблокада и блокада опсега",
"right-unblockself": "деблокирање самог себе",
"right-protect": "мењање нивоа заштите и уређивање страница под преносивом заштитом",
"right-editprotected": "уређивање страница под заштитом „{{int:protect-level-sysop}}“",
"right-editmyuserjs": "уређивање сопствених JavaScript датотека",
"right-viewmywatchlist": "преглед сопственог списка надгледања",
"right-editmywatchlist": "уређивање сопственог списка надгледања; неке предузете радње ће свеједно додати странице на списак и без овог права",
- "right-viewmyprivateinfo": "пÑ\80еглед Ñ\81воÑ\98иÑ\85 лиÑ\87ниÑ\85 подаÑ\82ака (нпÑ\80. имеÑ\98л адресу, право име)",
- "right-editmyprivateinfo": "Ñ\83Ñ\80еÑ\92иваÑ\9aе Ñ\81опÑ\81Ñ\82вениÑ\85 лиÑ\87ниÑ\85 подаÑ\82ака (нпÑ\80. имеÑ\98л адресе, правог имена)",
+ "right-viewmyprivateinfo": "пÑ\80еглед Ñ\81воÑ\98иÑ\85 пÑ\80иваÑ\82ниÑ\85 подаÑ\82ака (нпÑ\80. имеÑ\98л-адресу, право име)",
+ "right-editmyprivateinfo": "Ñ\83Ñ\80еÑ\92иваÑ\9aе Ñ\81опÑ\81Ñ\82вениÑ\85 пÑ\80иваÑ\82ниÑ\85 подаÑ\82ака (нпÑ\80. имеÑ\98л-адресе, правог имена)",
"right-editmyoptions": "уређивање сопствених подешавања",
"right-rollback": "брзо враћање измена последњег корисника који је мењао одређену страницу",
"right-markbotedits": "означавање враћених измена као измене бота",
"right-userrights": "уређивање свих корисничких права",
"right-userrights-interwiki": "уређивање корисничких права на другим викијима",
"right-siteadmin": "закључавање и откључавање базе података",
- "right-override-export-depth": "извоз Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\83кÑ\99Ñ\83Ñ\87Ñ\83Ñ\98Ñ\83Ñ\9bи и повазене Ñ\81Ñ\82Ñ\80аниÑ\86е до дÑ\83бине од пеÑ\82 веза",
+ "right-override-export-depth": "извоз Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\83кÑ\99Ñ\83Ñ\87Ñ\83Ñ\98Ñ\83Ñ\9bи и повазене Ñ\81Ñ\82Ñ\80аниÑ\86е до дÑ\83бине од пеÑ\82 линкова",
"right-sendemail": "слање имејла другим корисницима",
"right-managechangetags": "прављење и (де)активирање [[Special:Tags|ознака]]",
"right-applychangetags": "примењивање [[Special:Tags|ознака]] на нечије промене",
"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": "Уређивање вашег списка надгледања",
"grant-uploadeditmovefile": "Отпремање, замена и премештање датотека",
"grant-uploadfile": "Отпремање нових датотека",
"grant-basic": "Основна права",
- "grant-viewdeleted": "Ð\9fÑ\80еглед обрисаних страница и датотека",
+ "grant-viewdeleted": "Ð\9fÑ\80еглед избрисаних страница и датотека",
"grant-viewmywatchlist": "Преглед вашег списак надгледања",
"grant-viewrestrictedlogs": "Прегледање ограничених уноса у евиденцији",
"newuserlogpage": "Евиденција нових корисника",
"action-reupload-shared": "премостите ову датотеку са заједничког складишта",
"action-upload_by_url": "отпремите ову датотеку путем УРЛ-а",
"action-writeapi": "користите API за писање",
- "action-delete": "обришете ову страницу",
+ "action-delete": "избришете ову страницу",
"action-deleterevision": "бришете ревизије",
"action-deletelogentry": "бришете уносе у евиденцијама",
- "action-deletedhistory": "пÑ\80егледаÑ\82е обрисану историју странице",
- "action-deletedtext": "пÑ\80егледаÑ\82е обрисани текст ревизије",
- "action-browsearchive": "пÑ\80еÑ\82Ñ\80ажÑ\83Ñ\98еÑ\82е обрисане странице",
+ "action-deletedhistory": "пÑ\80егледаÑ\82е избрисану историју странице",
+ "action-deletedtext": "пÑ\80егледаÑ\82е избрисани текст ревизије",
+ "action-browsearchive": "пÑ\80еÑ\82Ñ\80ажÑ\83Ñ\98еÑ\82е избрисане странице",
"action-undelete": "враћате странице",
"action-suppressrevision": "прегледате и враћате сакривене ревизије",
"action-suppressionlog": "прегледате ову приватну евиденције",
"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-savedqueries-apply-label": "Направи филтер",
"rcfilters-savedqueries-apply-and-setdefault-label": "Направи подразумевани филтер",
"rcfilters-savedqueries-cancel-label": "Откажи",
- "rcfilters-savedqueries-add-new-title": "Сачувајте актуелна подешавања филтера",
+ "rcfilters-savedqueries-add-new-title": "Сачувајте тренутна подешавања филтера",
"rcfilters-savedqueries-already-saved": "Ови филтери су већ сачувани. Промените своја подешавања да бисте направили нове сачуване филтере.",
"rcfilters-restore-default-filters": "Врати подразумеване филтере",
"rcfilters-clear-all-filters": "Уклоните све филтере",
"rcfilters-show-new-changes": "Најновије промене",
"rcfilters-search-placeholder": "Филтрирајте промене (користите мени или претрагу за име филтера)",
- "rcfilters-invalid-filter": "Ð\9dеиÑ\81пÑ\80аван филтер",
+ "rcfilters-invalid-filter": "Ð\9dеважеÑ\9bи филтер",
"rcfilters-empty-filter": "Нема активних филтера. Сви доприноси су приказани.",
"rcfilters-filterlist-title": "Филтери",
"rcfilters-filterlist-whatsthis": "Како ово функционише?",
"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-description": "Све пÑ\80омене коÑ\98е ниÑ\81Ñ\83 â\80\9eпоÑ\81ледÑ\9aе Ñ\80евизиÑ\98еâ\80\9c.",
- "rcfilters-filter-excluded": "Изостављено",
+ "rcfilters-filter-previousrevision-label": "Ð\9dиÑ\98е наÑ\98новиÑ\98а ревизија",
+ "rcfilters-filter-previousrevision-description": "Све пÑ\80омене коÑ\98е ниÑ\81Ñ\83 â\80\9eпоÑ\81ледÑ\9aе Ñ\80евизиÑ\98еâ\80\9d.",
+ "rcfilters-filter-excluded": "Изузето",
"rcfilters-tag-prefix-namespace-inverted": "<strong>:није</strong> $1",
- "rcfilters-exclude-button-off": "Изостави означено",
- "rcfilters-exclude-button-on": "Изостави одабрано",
+ "rcfilters-exclude-button-off": "Изузми изабрано",
+ "rcfilters-exclude-button-on": "Изузми изабрано",
"rcfilters-view-tags": "Означене измене",
"rcfilters-view-namespaces-tooltip": "Филтрирајте резултате према именском простору",
"rcfilters-view-tags-tooltip": "Филтрирајте резултате према ознаци измене",
"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": "Сакриј побољшану верзију скорашњих измена",
"rc-old-title": "првобитно направљено као „$1“",
"recentchangeslinked": "Сродне промене",
"recentchangeslinked-feed": "Сродне измене",
- "recentchangeslinked-toolbox": "СÑ\80одне измене",
+ "recentchangeslinked-toolbox": "СÑ\80одне пÑ\80омене",
"recentchangeslinked-title": "Измене сродне са „$1“",
"recentchangeslinked-summary": "Унесите име странице да бисте видели промене на страницама које су повезане са или са те странице. (Да бисте видели чланове категорије, унесите {{ns:category}}:Име категорије). Промене на страницама које су на [[Special:Watchlist|Вашем списку надгледања]] су <strong>подебљане</strong>.",
"recentchangeslinked-page": "Назив странице:",
"upload_directory_missing": "Фасцикла за слање ($1) недостаје и сервер је не може направити.",
"upload_directory_read_only": "Сервер не може да пише по фасцикли за слање ($1).",
"uploaderror": "Грешка при отпремању",
- "upload-recreate-warning": "<strong>УпозоÑ\80еÑ\9aе: даÑ\82оÑ\82ека Ñ\81 Ñ\82им називом Ñ\98е обÑ\80иÑ\81ана или пÑ\80емеÑ\88Ñ\82ена.</strong>\n\nÐ\95виденÑ\86иÑ\98а бÑ\80иÑ\81аÑ\9aа и пÑ\80емеÑ\88Ñ\82аÑ\9aа Ñ\81е налази иÑ\81под:",
+ "upload-recreate-warning": "<strong>УпозоÑ\80еÑ\9aе: Ð\94аÑ\82оÑ\82ека Ñ\81а Ñ\82им именом Ñ\98е избÑ\80иÑ\81ана или пÑ\80емеÑ\88Ñ\82ена.</strong>\n\nÐ\95виденÑ\86иÑ\98а бÑ\80иÑ\81аÑ\9aа и пÑ\80емеÑ\88Ñ\82аÑ\9aа Ñ\81Ñ\82Ñ\80аниÑ\86е наведена Ñ\98е иÑ\81под Ñ\81а обÑ\80азложеÑ\9aем:",
"uploadtext": "Користите образац испод да бисте отпремили датотеке.\nЗа преглед или претрагу постојећих датотека, погледајте [[Special:FileList|списак отпремљених датотека]], поновна отпремања су наведена у [[Special:Log/upload|евиденцији отпремања]], а брисања у [[Special:Log/delete|евиденцији брисања]].\n\nДатотеку додајете на жељену страницу користећи следеће обрасце:\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Слика.jpg]]</nowiki></code>''' за верзију слике у пуној величини\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Слика.png|200п|мини|лево|опис]]</nowiki></code>''' за верзију слике с величином од 200 пиксела која је приказана у засебном оквиру, заједно с описом.\n* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Датотека.ogg]]</nowiki></code>''' за директно повезивање с датотеком без њеног приказивања",
"upload-permitted": "Дозвољени {{PLURAL:$2|тип|типови}} датотека: $1.",
"upload-preferred": "Препоручени {{PLURAL:$2|тип|типови}} датотека: $1.",
"ignorewarning": "Занемари упозорења и сачувај датотеку",
"ignorewarnings": "Занемари сва упозорења",
"minlength1": "Назив датотеке мора имати барем један знак.",
- "illegalfilename": "Ð\94аÑ\82оÑ\82ека â\80\9e$1â\80\9c Ñ\81адÑ\80жи знакове коÑ\98и ниÑ\81Ñ\83 дозвоÑ\99ени Ñ\83 називима Ñ\81Ñ\82Ñ\80аниÑ\86а.\nÐ\9fÑ\80омениÑ\82е назив даÑ\82оÑ\82еке и поново Ñ\98е поÑ\88аÑ\99ите.",
+ "illegalfilename": "Ð\98ме даÑ\82оÑ\82еке â\80\9e$1â\80\9c Ñ\81адÑ\80жи знакове коÑ\98и ниÑ\81Ñ\83 дозвоÑ\99ени Ñ\83 наÑ\81ловима Ñ\81Ñ\82Ñ\80аниÑ\86а.\nÐ\9fÑ\80еименÑ\83Ñ\98Ñ\82е даÑ\82оÑ\82екÑ\83 и покÑ\83Ñ\88аÑ\82е да Ñ\98е поново оÑ\82пÑ\80емите.",
"filename-toolong": "Називи датотека могу имати највише 240 бајтова.",
"badfilename": "Име датотеке је промењено у „$1“.",
"filetype-mime-mismatch": "Проширење датотеке „.$1“ не одговара препознатом типу MIME датотеке ($2).",
"large-file": "Препоручљиво је да датотеке не буду веће од $1; ова датотека је $2.",
"largefileserver": "Ова датотека прелази ограничење величине.",
"emptyfile": "Датотека коју сте послали је празна.\nУзрок може бити грешка у називу датотеке.\nПроверите да ли заиста желите да је пошаљете.",
- "windows-nonascii-filename": "Ð\9eваÑ\98 вики не подÑ\80жава називе даÑ\82оÑ\82ека Ñ\81 посебним знацима.",
+ "windows-nonascii-filename": "Ð\9eваÑ\98 вики не подÑ\80жава имена даÑ\82оÑ\82ека Ñ\81а посебним знацима.",
"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-shared-forbidden": "Датотека са овим именом већ постоји у заједничкој остави.\nАко још увек желите да отпремите датотеку, вратите се и користите ново име.\n[[File:$1|thumb|center|$1]]",
"fileexists-no-change": "Датотека је дупликат актуелне верзије <strong>[[:$1]]</strong>.",
"fileexists-duplicate-version": "Датотека је дупликат {{PLURAL:$2|старе верзије|старих верзија}} <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Ово је дупликат {{PLURAL:$1|следеће датотеке|следећих датотека}}:",
- "file-deleted-duplicate": "Ð\94аÑ\82оÑ\82ека иÑ\81Ñ\82овеÑ\82на овоÑ\98 ([[:$1]]) Ñ\98е пÑ\80еÑ\82Ñ\85одно обÑ\80иÑ\81ана.\nÐ\9fогледаÑ\98Ñ\82е иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 бÑ\80иÑ\81аÑ\9aа пÑ\80е поновног Ñ\81лаÑ\9aа.",
- "file-deleted-duplicate-notitle": "Ð\94аÑ\82оÑ\82ека иденÑ\82иÑ\87на овоÑ\98 пÑ\80еÑ\82Ñ\85одно Ñ\98е обÑ\80иÑ\81ана и име Ñ\98оÑ\98 Ñ\98е Ñ\81акÑ\80ивено.\nТÑ\80ебали биÑ\81Ñ\82е пиÑ\82аÑ\82и некога ко може видети податке скривених датотека да прегледа ситуацију пре него што поново отпремите датотеку.",
+ "file-deleted-duplicate": "Ð\94аÑ\82оÑ\82ека коÑ\98а Ñ\98е иденÑ\82иÑ\87на овоÑ\98 ([[:$1]]) Ñ\98е Ñ\80аниÑ\98е била избÑ\80иÑ\81ана.\nТÑ\80ебаÑ\82е да пÑ\80овеÑ\80иÑ\82е иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 бÑ\80иÑ\81аÑ\9aа Ñ\82е даÑ\82оÑ\82еке пÑ\80е него Ñ\88Ñ\82о наÑ\81Ñ\82авиÑ\82е Ñ\81а Ñ\9aеним поновним опÑ\82Ñ\80емаÑ\9aем.",
+ "file-deleted-duplicate-notitle": "Ð\94аÑ\82оÑ\82ека коÑ\98а Ñ\98е иденÑ\82иÑ\87на овоÑ\98 Ñ\80аниÑ\98е Ñ\98е избÑ\80иÑ\81ана и име Ñ\98оÑ\98 Ñ\98е Ñ\81акÑ\80ивено.\nТÑ\80ебаÑ\82е да пиÑ\82аÑ\82е некога ко може видети податке скривених датотека да прегледа ситуацију пре него што поново отпремите датотеку.",
"uploadwarning": "Упозорење при отпремању",
"uploadwarning-text": "Измените опис датотеке и покушајте поново.",
"uploadwarning-text-nostash": "Ре-отпремите датотеку, измените опис испод и покушајте поново.",
"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 датотекама.",
"upload-description": "Опис датотеке",
"upload-options": "Опције отпремања",
"watchthisupload": "Надгледај ову датотеку",
- "filewasdeleted": "Датотека с овим називом је раније послата, али је обрисана.\nПроверите $1 пре него што наставите с поновним слањем.",
+ "filewasdeleted": "Датотека са овим именом је раније оптремљена и након тога избрисана.\nТребате да проверите $1 пре него што наставите са њеним поновним оптремањем.",
"filename-bad-prefix": "Назив датотеке коју шаљете почиње са <strong>„$1“</strong>, а њега обично додељују дигитални фотоапарати.\nИзаберите назив датотеке који описује њен садржај.",
"filename-prefix-blacklist": " #<!-- оставите овај ред онаквим какав јесте --> <pre>\n# Синтакса је следећа:\n# * Све од тарабе па до краја реда је коментар\n# * Сваки ред означава префикс типичних назива датотека које додељивају дигитални апарати\nCIMG # Касио\nDSC_ # Никон\nDSCF # Фуџи\nDSCN # Никон\nDUW # неки мобилни телефони\nIMG # опште\nJD # Џеноптик\nMGP # Пентакс\nPICT # разно\n #</pre> <!-- оставите овај ред онаквим какав јесте -->",
- "upload-proto-error": "Ð\9dеиÑ\81пÑ\80аван протокол",
+ "upload-proto-error": "Ð\9dеважеÑ\9bи протокол",
"upload-proto-error-text": "Слање са спољне локације захтева адресу која почиње са <code>http://</code> или <code>ftp://</code>.",
"upload-file-error": "Унутрашња грешка",
"upload-file-error-text": "Дошло је до унутрашње грешке при отварању привремене датотеке на серверу.\nКонтактирајте [[Special:ListUsers/sysop|администратора]].",
"backend-fail-hashes": "Не могу да добијем дисперзије датотеке за упоређивање.",
"backend-fail-notsame": "Већ постоји неистоветна датотека – $1.",
"backend-fail-invalidpath": "$1 није важећа путања за складиштење.",
- "backend-fail-delete": "Ð\9dе могÑ\83 да обришем датотеку „$1”.",
+ "backend-fail-delete": "Ð\9dе могÑ\83 да избришем датотеку „$1”.",
"backend-fail-describe": "Не могу да променим метаподатке за датотеку „$1“.",
"backend-fail-alreadyexists": "Датотека $1 већ постоји.",
"backend-fail-store": "Не могу да сместим датотеку $1 у $2.",
"filejournal-fail-dbquery": "Не могу да ажурирам новинарску базу за складишну основу „$1“.",
"lockmanager-notlocked": "Не могу да откључам „$1“ јер није закључан.",
"lockmanager-fail-closelock": "Не могу да затворим катанац за „$1“.",
- "lockmanager-fail-deletelock": "Ð\9dе могÑ\83 да обришем катанац за „$1“.",
+ "lockmanager-fail-deletelock": "Ð\9dе могÑ\83 да избришем катанац за „$1“.",
"lockmanager-fail-acquirelock": "Не могу да се закључам за „$1“.",
"lockmanager-fail-openlock": "Не могу да отворим катанац за „$1“. Уверите се да је ваш директоријум за отпремање исправно конфигурисан и да ваш веб-сервер има дозволу да пише у том директоријуму. Погледајте https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgUploadDirectory за више информација.",
"lockmanager-fail-releaselock": "Не могу да ослободим катанац за „$1“.",
"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": "Не могу наћи путању или ово није обична датотека.",
"uploadstash-no-such-key": "Нема таквог кључа ($1). Не могу уклонити.",
"uploadstash-no-extension": "Додатак је празан.",
"uploadstash-zero-length": "Датотека је празна",
- "invalid-chunk-offset": "Ð\9dеиÑ\81пÑ\80авна полазна тачка",
+ "invalid-chunk-offset": "Ð\9dеважеÑ\9bа полазна тачка",
"img-auth-accessdenied": "Приступ је одбијен",
"img-auth-nopathinfo": "Недостаје PATH_INFO.\nВаш сервер није подешен да прослеђује овакве податке.\nМожда је заснован на CGI-ју који не подржава img_auth.\nПогледајте https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization?uselang=sr-ec.",
- "img-auth-notindir": "Ð\97аÑ\85Ñ\82евана пÑ\83Ñ\82аÑ\9aа ниÑ\98е Ñ\83 подеÑ\88еноÑ\98 Ñ\84аÑ\81Ñ\86икли за отпремање.",
- "img-auth-badtitle": "Не могу да направим валидан наслов за „$1“.",
+ "img-auth-notindir": "ТÑ\80ажена пÑ\83Ñ\82аÑ\9aа ниÑ\98е Ñ\83 подеÑ\88еном диÑ\80екÑ\82оÑ\80иÑ\98Ñ\83мÑ\83 за отпремање.",
+ "img-auth-badtitle": "Не могу да саставим важећи наслов из „$1“.",
"img-auth-nologinnWL": "Нисте пријављени и „$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-noread": "Корисник нема приступ за читање „$1“.",
- "http-invalid-url": "Ð\9dеиÑ\81пÑ\80авна адÑ\80еÑ\81а: $1",
+ "http-invalid-url": "Ð\9dеважеÑ\9bи URL: $1",
"http-invalid-scheme": "Адресе са шемом „$1“ нису подржане.",
"http-request-error": "HTTP захтев није прошао због непознате грешке.",
"http-read-error": "HTTP грешка при читању.",
"licenses-edit": "Уреди избор лиценци",
"license-nopreview": "(преглед није доступан)",
"upload_source_url": "(ваша изабрана датотека од важећих, јавно доступних адреса)",
- "upload_source_file": "(ваша одабрана датотека са вашег рачунара)",
- "listfiles-delete": "обриши",
+ "upload_source_file": "(ваша одабрана датотека са рачунара)",
+ "listfiles-delete": "избриши",
"listfiles-summary": "Ова посебна страница приказује све отпремљене датотеке.",
"listfiles_search_for": "Назив датотеке:",
"listfiles-userdoesnotexist": "Кориснички налог „$1“ није отворен.",
"imgfile": "датотека",
"listfiles": "Списак датотека",
- "listfiles_thumb": "Ð\9cиниÑ\98аÑ\82Ñ\83Ñ\80а",
+ "listfiles_thumb": "СлиÑ\87иÑ\86а",
"listfiles_date": "Датум",
"listfiles_name": "Назив",
"listfiles_user": "Корисник",
"file-anchor-link": "Датотека",
"filehist": "Историја датотеке",
"filehist-help": "Кликните на датум/време да бисте видели тадашњу верзију датотеке.",
- "filehist-deleteall": "обриши све",
- "filehist-deleteone": "обриши",
+ "filehist-deleteall": "избриши све",
+ "filehist-deleteone": "избриши",
"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": "Величина датотеке",
"filehist-comment": "Коментар",
"imagelinks": "Употреба датотеке",
"linkstoimage": "{{PLURAL:$1|Следећа страница користи|$1 следеће странице користе|$1 следећих страница користи}} ову датотеку:",
- "linkstoimage-more": "Ð\92иÑ\88е од $1 {{PLURAL:$1|Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\80иÑ\81Ñ\82и|Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\80иÑ\81Ñ\82е|Ñ\81Ñ\82Ñ\80аниÑ\86а коÑ\80иÑ\81Ñ\82е}} овÑ\83 даÑ\82оÑ\82екÑ\83.\nСледеÑ\9bи Ñ\81пиÑ\81ак пÑ\80иказÑ\83Ñ\98е Ñ\81амо {{PLURAL:$1|пÑ\80вÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 коÑ\98а коÑ\80иÑ\81Ñ\82и|пÑ\80ве $1 Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\98е коÑ\80иÑ\81Ñ\82е|пÑ\80виÑ\85 $1 Ñ\81Ñ\82Ñ\80аниÑ\86а коÑ\98е коÑ\80иÑ\81Ñ\82е}} ову датотеку.\nДоступан је и [[Special:WhatLinksHere/$2|потпуни списак]].",
+ "linkstoimage-more": "Ð\92иÑ\88е од $1 {{PLURAL:$1|Ñ\81Ñ\82Ñ\80аниÑ\86а коÑ\80иÑ\81Ñ\82и|Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\80иÑ\81Ñ\82е|Ñ\81Ñ\82Ñ\80аниÑ\86а коÑ\80иÑ\81Ñ\82и}} овÑ\83 даÑ\82оÑ\82екÑ\83.\nСледеÑ\9bи Ñ\81пиÑ\81ак пÑ\80иказÑ\83Ñ\98е {{PLURAL:$1|пÑ\80вÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 коÑ\98а коÑ\80иÑ\81Ñ\82и|пÑ\80ве $1 Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\98е коÑ\80иÑ\81Ñ\82е|пÑ\80виÑ\85 $1 Ñ\81Ñ\82Ñ\80аниÑ\86а коÑ\98е коÑ\80иÑ\81Ñ\82е}} Ñ\81амо ову датотеку.\nДоступан је и [[Special:WhatLinksHere/$2|потпуни списак]].",
"nolinkstoimage": "Нема страница које користе ову датотеку.",
- "morelinkstoimage": "Ð\9fогледаÑ\98Ñ\82е [[Special:WhatLinksHere/$1|виÑ\88е веза]] до ове датотеке.",
+ "morelinkstoimage": "Ð\9fогледаÑ\98Ñ\82е [[Special:WhatLinksHere/$1|виÑ\88е линкова]] до ове датотеке.",
"linkstoimage-redirect": "$1 (преусмерење датотеке) $2",
"duplicatesoffile": "{{PLURAL:$1|Следећа датотека је дупликат|Следеће $1 датотеке су дупликати|Следећих $1 датотека су дупликати}} ове датотеке ([[Special:FileDuplicateSearch/$2|детаљније]]):",
"sharedupload": "Ова датотека се налази на $1 и може се користити и на другим пројектима.",
"sharedupload-desc-edit": "Ова датотека се налази на $1 и може да се користи на другим пројектима.\nЊен опис можете да измените на [$2 одговарајућој страници].",
"sharedupload-desc-create": "Ова датотека се налази на $1 и може да се користи на другим пројектима.\nЊен опис можете да измените на [$2 одговарајућој страници].",
"filepage-nofile": "Не постоји датотека с овим називом.",
- "filepage-nofile-link": "Не постоји датотека с овим називом, али је можете [$1 послати].",
+ "filepage-nofile-link": "Не постоји датотека са овим именом, али је можете [$1 опремити].",
"uploadnewversion-linktext": "Отпреми нову верзију ове датотеке",
"shared-repo-from": "из $1",
"shared-repo": "заједничко складиште",
"filerevert-success": "Датотека <strong>[[Media:$1|$1]]</strong> је враћена на [$4 верзију од $2; $3].",
"filerevert-badversion": "Не постоји ранија локална верзија ове датотеке са наведеном временском ознаком.",
"filerevert-identical": "Актуелна верзија датотеке је индентична изабраној.",
- "filedelete": "Ð\9eбÑ\80иÑ\88и $1",
- "filedelete-legend": "Ð\9eбриши датотеку",
+ "filedelete": "Ð\91Ñ\80иÑ\81аÑ\9aе даÑ\82оÑ\82еке/Ñ\81Ñ\82Ñ\80аниÑ\86е $1",
+ "filedelete-legend": "Ð\98збриши датотеку",
"filedelete-intro": "Бришете датотеку '''[[Media:$1|$1]]''' заједно с њеном историјом.",
"filedelete-intro-old": "Бришете верзију датотеке '''[[Media:$1|$1]]''' од [$4 $2; $3].",
"filedelete-comment": "Разлог:",
- "filedelete-submit": "Ð\9eбриши",
- "filedelete-success": "Датотека '''$1''' је обрисана.",
- "filedelete-success-old": "Ð\92еÑ\80зиÑ\98а даÑ\82оÑ\82еке <strong>[[Media:$1|$1]]</strong> од $2, $3 Ñ\98е обрисана.",
+ "filedelete-submit": "Ð\98збриши",
+ "filedelete-success": "Датотека <strong>$1</strong> је избрисана.",
+ "filedelete-success-old": "Ð\92еÑ\80зиÑ\98а даÑ\82оÑ\82еке <strong>[[Media:$1|$1]]</strong> од $2, $3 Ñ\98е избрисана.",
"filedelete-nofile": "Датотека '''$1''' не постоји.",
"filedelete-nofile-old": "Не постоји архивирана верзија датотеке <strong>$1</strong> са наведеним особинама.",
"filedelete-otherreason": "Други/додатни разлог:",
"filedelete-reason-dropdown": "*Најчешћи разлози брисања\n** Кршење ауторских права\n** Дупликати датотека",
"filedelete-edit-reasonlist": "Уреди разлоге брисања",
"filedelete-maintenance": "Брисање и враћање датотека је привремено онемогућено због одржавања.",
- "filedelete-maintenance-title": "Ð\9dе могÑ\83 да обришем датотеку",
+ "filedelete-maintenance-title": "Ð\9dе могÑ\83 да избришем датотеку",
"mimesearch": "MIME претрага",
"mimesearch-summary": "Ова страница омогућава филтрирање датотека према њиховим MIME типовима.\nУлазни подаци: contenttype/subtype или contenttype/*, нпр. <code>image/jpeg</code>.",
"mimetype": "MIME тип:",
"listduplicatedfiles-entry": "[[:File:$1|$1]] има [[$3|{{PLURAL:$2|један дупликат|$2 дупликата}}]].",
"unusedtemplates": "Некоришћени шаблони",
"unusedtemplatestext": "Ова страница наводи све странице у именском простору {{ns:template}} које нису укључене ни на једној другој страници.\nПре брисања проверите да ли друге странице воде до тих шаблона.",
- "unusedtemplateswlh": "оÑ\81Ñ\82але везе",
+ "unusedtemplateswlh": "оÑ\81Ñ\82али линкови",
"randompage": "Случајна страница",
"randompage-nopages": "Нема страница у {{PLURAL:$2|следећем именском простору|следећим именским просторима}}: $1.",
"randomincategory": "Случајна страница у категорији",
"statistics-header-users": "Корисници",
"statistics-header-hooks": "Остало",
"statistics-articles": "Странице са садржајем",
- "statistics-pages": "СÑ\82Ñ\80аниÑ\86а",
+ "statistics-pages": "СÑ\82Ñ\80аниÑ\86е",
"statistics-pages-desc": "Све странице на викију, укључујући странице за разговор, преусмерења итд.",
- "statistics-files": "Ð\91Ñ\80оÑ\98 поÑ\81лаÑ\82иÑ\85 даÑ\82оÑ\82ека",
+ "statistics-files": "Ð\9eÑ\82пÑ\80емÑ\99ене даÑ\82оÑ\82еке",
"statistics-edits": "Број измена страница откад постоји {{SITENAME}}",
"statistics-edits-average": "Просечан број измена по страници",
"statistics-users": "Регистровани корисници",
"pageswithprop-prophidden-long": "сакривено дуго текстуално својство ($1)",
"pageswithprop-prophidden-binary": "сакривено дуго бинарно својство ($1)",
"doubleredirects": "Двострука преусмерења",
- "doubleredirectstext": "Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а пÑ\80иказÑ\83Ñ\98е Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\98е пÑ\80еÑ\83Ñ\81меÑ\80аваÑ\98Ñ\83 на дÑ\80Ñ\83га пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа.\nСваки Ñ\80ед Ñ\81адÑ\80жи везе према првом и другом преусмерењу, као и одредишну страницу другог преусмерења која је обично „прави“ чланак на кога прво преусмерење треба да упућује.\n<del>Прецртани</del> уноси су већ решени.",
+ "doubleredirectstext": "Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а пÑ\80иказÑ\83Ñ\98е Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\98е пÑ\80еÑ\83Ñ\81меÑ\80аваÑ\98Ñ\83 на дÑ\80Ñ\83га пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа.\nСваки Ñ\80ед Ñ\81адÑ\80жи линкове према првом и другом преусмерењу, као и одредишну страницу другог преусмерења која је обично „прави“ чланак на кога прво преусмерење треба да упућује.\n<del>Прецртани</del> уноси су већ решени.",
"double-redirect-fixed-move": "[[$1]] је премештен.\nАутоматски је ажурирано и сада преусмерава на [[$2]].",
"double-redirect-fixed-maintenance": "Аутоматски исправља двострука преусмерења из [[$1]] у [[$2]] као део одржавања",
"double-redirect-fixer": "Исправљач преусмерења",
"brokenredirects": "Покварена преусмерења",
- "brokenredirectstext": "Следећа преусмерења упућују на непостојеће странице:",
+ "brokenredirectstext": "Следећа преусмерења воде на непостојеће странице:",
"brokenredirects-edit": "уреди",
- "brokenredirects-delete": "обриши",
- "withoutinterwiki": "СÑ\82Ñ\80аниÑ\86е без Ñ\98езиÑ\87киÑ\85 веза",
- "withoutinterwiki-summary": "СледеÑ\9bе Ñ\81Ñ\82Ñ\80аниÑ\86е ниÑ\81Ñ\83 повезане Ñ\81 другим језицима.",
+ "brokenredirects-delete": "избриши",
+ "withoutinterwiki": "СÑ\82Ñ\80аниÑ\86е без Ñ\98езиÑ\87киÑ\85 линкова",
+ "withoutinterwiki-summary": "СледеÑ\9bе Ñ\81Ñ\82Ñ\80аниÑ\86е немаÑ\98Ñ\83 линкове пÑ\80ема веÑ\80зиÑ\98ама на другим језицима.",
"withoutinterwiki-legend": "Префикс",
"withoutinterwiki-submit": "Прикажи",
"fewestrevisions": "Странице са најмање ревизија",
"nbytes": "$1 {{PLURAL:$1|бајт|бајта|бајтова}}",
"ncategories": "$1 {{PLURAL:$1|категорија|категорије|категорија}}",
"ninterwikis": "$1 {{PLURAL:$1|међувики|међувикија|међувикија}}",
- "nlinks": "$1 {{PLURAL:$1|веза|везе|веза}}",
+ "nlinks": "$1 {{PLURAL:$1|линк|линка|линкова}}",
"nmembers": "$1 {{PLURAL:$1|члан|члана|чланова}}",
"nmemberschanged": "$1 → $2 {{PLURAL:$2|члан|члана|чланова}}",
"nrevisions": "$1 {{PLURAL:$1|ревизија|ревизије|ревизија}}",
"unusedimages": "Некоришћене датотеке",
"wantedcategories": "Тражене категорије",
"wantedpages": "Тражене странице",
- "wantedpages-summary": "СпиÑ\81ак непоÑ\81Ñ\82оÑ\98еÑ\9bиÑ\85 Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81а наÑ\98виÑ\88е веза до њих, на овом списку се не налазе странице до којих воде преусмерења. За списак покварених преусмерења погледајте [[{{#special:BrokenRedirects}}|списак покварених преусмерења]].",
- "wantedpages-badtitle": "Ð\9dеиÑ\81пÑ\80аван наÑ\81лов Ñ\83 Ñ\81еÑ\82у резултата: $1",
+ "wantedpages-summary": "СпиÑ\81ак непоÑ\81Ñ\82оÑ\98еÑ\9bиÑ\85 Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81а наÑ\98виÑ\88е линкова до њих, на овом списку се не налазе странице до којих воде преусмерења. За списак покварених преусмерења погледајте [[{{#special:BrokenRedirects}}|списак покварених преусмерења]].",
+ "wantedpages-badtitle": "Ð\9dевалидан наÑ\81лов Ñ\83 Ñ\81кÑ\83пу резултата: $1",
"wantedfiles": "Тражене датотеке",
"wantedfiletext-cat": "Следеће датотеке се користе, али не постоје. Датотеке из других ризница могу бити наведене иако не постоје. Такве датотеке ће бити <del>поништене</del> са списка. Поред тога, странице које садрже непостојеће датотеке се налазе [[:$1|овде]].",
"wantedfiletext-nocat": "Следеће датотеке се користе, али не постоје. Датотеке из других ризница могу бити наведене иако не постоје. Такве датотеке ће бити <del>поништене</del> са списка.",
"wantedfiletext-nocat-noforeign": "Следеће датотеке се користе, али не постоје.",
"wantedtemplates": "Тражени шаблони",
- "mostlinked": "СÑ\82Ñ\80аниÑ\86е Ñ\81а наÑ\98виÑ\88е веза",
- "mostlinkedcategories": "Ð\9aаÑ\82егоÑ\80иÑ\98е Ñ\81а наÑ\98виÑ\88е веза",
- "mostlinkedtemplates": "СÑ\82Ñ\80аниÑ\86е Ñ\81а наÑ\98виÑ\88е веза",
+ "mostlinked": "СÑ\82Ñ\80аниÑ\86е Ñ\81а наÑ\98виÑ\88е линкова",
+ "mostlinkedcategories": "Ð\9aаÑ\82егоÑ\80иÑ\98е Ñ\81а наÑ\98виÑ\88е линкова",
+ "mostlinkedtemplates": "СÑ\82Ñ\80аниÑ\86е Ñ\81а наÑ\98виÑ\88е линкова",
"mostcategories": "Странице са највише категорија",
- "mostimages": "Ð\94аÑ\82оÑ\82еке Ñ\81а наÑ\98виÑ\88е веза",
+ "mostimages": "Ð\94аÑ\82оÑ\82еке Ñ\81а наÑ\98виÑ\88е линкова",
"mostinterwikis": "Странице са највише међувикија",
"mostrevisions": "Странице са највише ревизија",
"prefixindex": "Све странице са префиксом",
"shortpages": "Кратке странице",
"longpages": "Дугачке странице",
"deadendpages": "Ћорсокаци",
- "deadendpagestext": "СледеÑ\9bе Ñ\81Ñ\82Ñ\80аниÑ\86е немаÑ\98Ñ\83 везе до других страница на овом викију.",
+ "deadendpagestext": "СледеÑ\9bе Ñ\81Ñ\82Ñ\80аниÑ\86е немаÑ\98Ñ\83 линкове до других страница на овом викију.",
"protectedpages": "Заштићене странице",
"protectedpages-filters": "Филтери:",
"protectedpages-indef": "Само неограничене заштите",
"apisandbox-submit-invalid-fields-title": "Нека поља нису валидна",
"apisandbox-submit-invalid-fields-message": "Молимо Вас поправите означена поља и покушајте поново.",
"apisandbox-results": "Резултати",
- "apisandbox-sending-request": "СлаÑ\9aе API заÑ\85Ñ\82ева...",
+ "apisandbox-sending-request": "ШаÑ\99ем API заÑ\85Ñ\82евâ\80¦",
"apisandbox-loading-results": "Пријем API резултата...",
"apisandbox-results-error": "Дошло је до грешке приликом учитавања резултата API упита: $1.",
"apisandbox-request-selectformat-label": "Прикажи сахтеване податке као:",
"apisandbox-request-time": "Време за извршавање захтјева: {{PLURAL:$1|$1 милисекунда|$1 милисекунде|$1 милисекунди}}",
"apisandbox-results-fixtoken": "Исправи токен и пошаљи поново",
"apisandbox-results-fixtoken-fail": "Неуспело добијање „$1“ токена.",
- "apisandbox-alert-page": "Поља на страници су неисправна.",
- "apisandbox-alert-field": "Вредност овог поља је неисправна.",
+ "apisandbox-alert-page": "Поља на страници нису важећа.",
+ "apisandbox-alert-field": "Вредност овог поља није важећа.",
"apisandbox-continue": "Настави",
"apisandbox-continue-clear": "Очисти",
"apisandbox-param-limit": "Унесите <kbd>max</kbd> да би сте користили највеће ограничење.",
"booksources-search-legend": "Претражи штампане изворе",
"booksources-isbn": "ISBN:",
"booksources-search": "Претражи",
- "booksources-text": "Ð\98Ñ\81под Ñ\81е налази Ñ\81пиÑ\81ак веза ка сајтовима који се баве продајом нових и половних књига, а који би могли имати додатне податке о књигама које тражите:",
+ "booksources-text": "Ð\98Ñ\81под Ñ\81е налази Ñ\81пиÑ\81ак линкова ка сајтовима који се баве продајом нових и половних књига, а који би могли имати додатне податке о књигама које тражите:",
"booksources-invalid-isbn": "Наведени ISBN број није валидан. Проверите да није дошло до грешке при копирању из првобитног извора.",
- "magiclink-tracking-rfc": "Странице с магичним RFC везама",
- "magiclink-tracking-pmid": "Странице с магичним PMID везама",
- "magiclink-tracking-isbn": "СÑ\82Ñ\80аниÑ\86е Ñ\81а ISBN магиÑ\87ним везама",
+ "magiclink-tracking-rfc": "Странице са магичним RFC линковима",
+ "magiclink-tracking-pmid": "Странице са магичним PMID линковима",
+ "magiclink-tracking-isbn": "СÑ\82Ñ\80аниÑ\86е Ñ\81а ISBN магиÑ\87ним линковима",
"specialloguserlabel": "Извршилац:",
"speciallogtitlelabel": "Циљ (наслов или {{ns:user}}:корисничко име):",
"log": "Евиденције",
"logeventslist-submit": "Прикажи",
- "logeventslist-more-filters": "Ð\9fÑ\80иказ додаÑ\82ниÑ\85 дневника:",
+ "logeventslist-more-filters": "Ð\9fÑ\80иказ додаÑ\82ниÑ\85 евиденÑ\86иÑ\98а:",
"logeventslist-patrol-log": "Евиденција патролирања",
"logeventslist-tag-log": "Евиденција ознака",
"all-logs-page": "Све јавне евиденције",
"categories-submit": "Прикажи",
"categoriespagetext": "{{PLURAL:$1|1=Следећа категорија садржи|Следеће категорије садрже}} странице или датотеке.\n[[Special:UnusedCategories|Некоришћене категорије]] нису приказане овде.\nПогледајте и [[Special:WantedCategories|тражене категорије]].",
"categoriesfrom": "Прикажи категорије почев од:",
- "deletedcontributions": "Ð\9eбрисани кориснички доприноси",
- "deletedcontributions-title": "Ð\9eбрисани кориснички доприноси",
+ "deletedcontributions": "Ð\98збрисани кориснички доприноси",
+ "deletedcontributions-title": "Ð\98збрисани кориснички доприноси",
"sp-deletedcontributions-contribs": "доприноси",
- "linksearch": "Ð\9fÑ\80еÑ\82Ñ\80ага Ñ\81поÑ\99аÑ\88Ñ\9aиÑ\85 веза",
+ "linksearch": "Ð\9fÑ\80еÑ\82Ñ\80ага Ñ\81поÑ\99аÑ\88Ñ\9aиÑ\85 линкова",
"linksearch-pat": "Образац претраге:",
"linksearch-ns": "Именски простор:",
"linksearch-ok": "Претражи",
"linksearch-text": "Могу се користити џокери попут „*.wikipedia.org“.\nПотребан је највиши домен, као „*.org“.<br />\n{{PLURAL:$2|1=Подржан протокол|Подржани протоколи}}: $1 (задаје http:// ако не наведете протокол).",
- "linksearch-line": "$1 веза Ñ\83 $2",
+ "linksearch-line": "$1 води Ñ\81а $2",
"linksearch-error": "Џокери се могу појавити само на почетку адресе.",
"listusersfrom": "Прикажи кориснике почев од:",
"listusers-submit": "Прикажи",
"restricted-displaytitle-ignored": "Странице са занемареним насловима за приказ",
"noindex-category-desc": "Странице које у себи имају магичну реч <code><nowiki>__NOINDEX__</nowiki></code>.",
"index-category-desc": "Странице које у себи имају магичну реч <code><nowiki>__INDEX__</nowiki></code> и самим тим су индексиране од стране робота.",
- "broken-file-category-desc": "СÑ\82Ñ\80аниÑ\86е коÑ\98е имаÑ\98Ñ\83 везе до непоÑ\81Ñ\82оÑ\98еÑ\9bиÑ\85 даÑ\82оÑ\82ека.",
+ "broken-file-category-desc": "СÑ\82Ñ\80аниÑ\86а Ñ\81адÑ\80жи покваÑ\80ени линк до даÑ\82оÑ\82еке (линк за Ñ\83гÑ\80аÑ\92иваÑ\9aе даÑ\82оÑ\82еке када она не поÑ\81Ñ\82оÑ\98и).",
"hidden-category-category-desc": "Категорије које у себи имају магичну реч <code><nowiki>__HIDDENCAT__</nowiki></code> и самим тим се не приказују у одељку за категорије на страницама.",
"trackingcategories-nodesc": "Опис није доступан.",
"trackingcategories-disabled": "Категорија је онемогућена",
"defemailsubject": "{{SITENAME}} — Имејл од {{GENDER:$1|корисника|кориснице}} „$1”",
"usermaildisabled": "Кориснички имејл је онемогућен",
"usermaildisabledtext": "Не можете да шаљете имејлове другим корисницима на овом викију",
- "noemailtitle": "Нема имејл адресе",
- "noemailtext": "Ð\9eваÑ\98 коÑ\80иÑ\81ник ниÑ\98е навео валиднÑ\83 имеÑ\98л адресу.",
+ "noemailtitle": "Нема имејл-адресе",
+ "noemailtext": "Ð\9eваÑ\98 коÑ\80иÑ\81ник ниÑ\98е навео важеÑ\9bÑ\83 имеÑ\98л-адресу.",
"nowikiemailtext": "Овај корисник је одлучио да не прима имејлове од других корисника.",
- "emailnotarget": "Ð\9dепоÑ\81Ñ\82оÑ\98еÑ\9bе или неиÑ\81пÑ\80авно корисничко име примаоца.",
+ "emailnotarget": "Ð\9dепоÑ\81Ñ\82оÑ\98еÑ\9bе или наважеÑ\9bе корисничко име примаоца.",
"emailtarget": "Унос корисничког имена примаоца",
"emailusername": "Корисничко име:",
"emailusernamesubmit": "Пошаљи",
"unwatch": "Прекини надгледање",
"unwatchthispage": "Прекини надгледање",
"notanarticle": "Није страница са садржајем",
- "notvisiblerev": "Ð\9fоÑ\81ледÑ\9aа Ñ\80евизиÑ\98а дÑ\80Ñ\83гог коÑ\80иÑ\81ника Ñ\98е обрисана.",
+ "notvisiblerev": "Ð\9fоÑ\81ледÑ\9aа Ñ\80евизиÑ\98а дÑ\80Ñ\83гог коÑ\80иÑ\81ника Ñ\98е избрисана.",
"watchlist-details": "Имате {{PLURAL:$1|$1 страницу|$1 странице|$1 страница}} на свом списку надгледања (плус странице за разговор).",
"wlheader-enotif": "Обавештење имејлом је омогућено.",
"wlheader-showupdated": "Странице које су промењене откад сте их последњи пут посетили су <strong>подебљане</strong>.",
"enotif_minoredit": "Ово је мања измена",
"created": "направљена",
"changed": "измењена",
- "deletepage": "Ð\9eбриши страницу",
+ "deletepage": "Ð\98збриши страницу",
"confirm": "Потврди",
"excontent": "садржај је био: „$1“",
"excontentauthor": "садржај је био: „$1“, а једини уредник „[[Special:Contributions/$2|$2]]“ ([[User talk:$2|разговор]])",
"exbeforeblank": "садржај пре брисања је био: „$1“",
"delete-confirm": "Брисање странице „$1“",
"delete-legend": "Брисање",
- "historywarning": "<strong>Упозорење:</strong> страница коју желите да обришете има историју са $1 {{PLURAL:$1|ревизијом|ревизије|ревизија}}:",
+ "historywarning": "<strong>Упозорење:</strong> Страница коју желите да избришете има историју са $1 {{PLURAL:$1|ревизијом|ревизије|ревизија}}:",
"historyaction-submit": "Прикажи",
- "confirmdeletetext": "УпÑ\80аво Ñ\9bеÑ\82е обÑ\80иÑ\81аÑ\82и Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, Ñ\83кÑ\99Ñ\83Ñ\87Ñ\83Ñ\98Ñ\83Ñ\9bи и Ñ\9aенÑ\83 иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83.\nÐ\9fоÑ\82вÑ\80диÑ\82е Ñ\81воÑ\98Ñ\83 намеÑ\80Ñ\83, да Ñ\80азÑ\83меÑ\82е поÑ\81ледиÑ\86е и да ово Ñ\80адиÑ\82е Ñ\83 Ñ\81кладÑ\83 Ñ\81 [[{{MediaWiki:Policy-url}}|правилима]].",
+ "confirmdeletetext": "УпÑ\80аво Ñ\9bеÑ\82е избÑ\80иÑ\81аÑ\82и Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, Ñ\83кÑ\99Ñ\83Ñ\87Ñ\83Ñ\98Ñ\83Ñ\9bи и Ñ\9aенÑ\83 иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83.\nÐ\9fоÑ\82вÑ\80диÑ\82е Ñ\81воÑ\98Ñ\83 намеÑ\80Ñ\83, да Ñ\80азÑ\83меÑ\82е поÑ\81ледиÑ\86е и да ово Ñ\80адиÑ\82е Ñ\83 Ñ\81кладÑ\83 Ñ\81а [[{{MediaWiki:Policy-url}}|правилима]].",
"actioncomplete": "Радња је завршена",
"actionfailed": "Радња није успела",
- "deletedtext": "СÑ\82Ñ\80аниÑ\86а â\80\9e$1â\80\9c Ñ\98е обÑ\80иÑ\81ана.\nÐ\9fогледаÑ\98Ñ\82е ''$2'' за запис недавних брисања.",
+ "deletedtext": "СÑ\82Ñ\80аниÑ\86а â\80\9e$1â\80\9c Ñ\98е избÑ\80иÑ\81ана.\nÐ\9fогледаÑ\98Ñ\82е $2 за запис недавних брисања.",
"dellogpage": "Евиденција брисања",
"dellogpagetext": "Испод је списак недавних брисања.",
"deletionlog": "евиденција брисања",
"delete-edit-reasonlist": "Уреди разлоге брисања",
"delete-toobig": "Ова страница има велику историју измена, преко $1 {{PLURAL:$1|ревизија|ревизије|ревизија}}.\nБрисање таквих страница је ограничено да би се спречило случајно оптерећење сервера.",
"delete-warning-toobig": "Ова страница има велику историју измена, преко $1 {{PLURAL:$1|ревизија|ревизије|ревизија}}.\nЊено брисање може да поремети базу података, стога поступајте с опрезом.",
- "deleteprotected": "Ð\9dе можеÑ\82е обÑ\80иÑ\81аÑ\82и овÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 заÑ\82о Ñ\88Ñ\82о је заштићена.",
+ "deleteprotected": "Ð\9dе можеÑ\82е да избÑ\80иÑ\88еÑ\82е овÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 Ñ\98еÑ\80 је заштићена.",
"deleting-backlinks-warning": "<strong>Упозорење:</strong> бришете страницу која је укључена у [[Special:WhatLinksHere/{{FULLPAGENAME}}|друге странице]] или друге странице воде на њу.",
- "deleting-subpages-warning": "<strong>Ð\9fажÑ\9aа:</strong> СÑ\82Ñ\80аниÑ\86Ñ\83 коÑ\98Ñ\83 желиÑ\82е обрисати има [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|подстраницу|$1 подстранице|$1 подстраница|51=преко 50 подстраница}}]].",
+ "deleting-subpages-warning": "<strong>УпозоÑ\80еÑ\9aе:</strong> СÑ\82Ñ\80аниÑ\86а коÑ\98Ñ\83 желиÑ\82е избрисати има [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|подстраницу|$1 подстранице|$1 подстраница|51=преко 50 подстраница}}]].",
"rollback": "Врати измене",
"rollbacklink": "врати",
"rollbacklinkcount": "врати $1 {{PLURAL:$1|измену|измене|измена}}",
"protect-legend": "Подешавања заштите",
"protectcomment": "Разлог:",
"protectexpiry": "Истиче:",
- "protect_expiry_invalid": "Време истека није важеће.",
+ "protect_expiry_invalid": "Време истека је неважеће.",
"protect_expiry_old": "Време истека је у прошлости.",
"protect-unchain-permissions": "Откључај даљња подешавања заштите",
"protect-text": "Овде можете да погледате и промените ниво заштите странице <strong>$1</strong>.",
"restriction-level-sysop": "потпуно заштићено",
"restriction-level-autoconfirmed": "полузаштићено",
"restriction-level-all": "сви нивои",
- "undelete": "Ð\9fÑ\80еглед обрисаних страница",
- "undeletepage": "Ð\9fÑ\80еглед и вÑ\80аÑ\9bаÑ\9aе обрисаних страница",
- "undeletepagetitle": "<strong>СледеÑ\9bи Ñ\81адÑ\80жаÑ\98 Ñ\81е Ñ\81аÑ\81Ñ\82оÑ\98и од обрисаних ревизија странице [[:$1|$1]]</strong>.",
- "viewdeletedpage": "Ð\9fÑ\80иказ обрисаних страница",
- "undeletepagetext": "{{PLURAL:$1|СледеÑ\9bа Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е обÑ\80иÑ\81ана, али Ñ\98е Ñ\98оÑ\88 Ñ\83 аÑ\80Ñ\85иви и може биÑ\82и вÑ\80аÑ\9bена|СледеÑ\9bе $1 Ñ\81Ñ\82Ñ\80аниÑ\86е Ñ\81Ñ\83 обÑ\80иÑ\81ане, али Ñ\81Ñ\83 Ñ\98оÑ\88 Ñ\83 аÑ\80Ñ\85иви и могÑ\83 биÑ\82и вÑ\80аÑ\9bене|СледеÑ\9bиÑ\85 $1 Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е обрисано, али су још у архиви и могу бити враћене}}.\nАрхива се повремено чисти од оваквих страница.",
+ "undelete": "Ð\9fÑ\80еглед избрисаних страница",
+ "undeletepage": "Ð\9fÑ\80еглед и вÑ\80аÑ\9bаÑ\9aе избрисаних страница",
+ "undeletepagetitle": "<strong>СледеÑ\9bи Ñ\81адÑ\80жаÑ\98 Ñ\81е Ñ\81аÑ\81Ñ\82оÑ\98и од избрисаних ревизија странице [[:$1|$1]]</strong>.",
+ "viewdeletedpage": "Ð\9fÑ\80еглед избрисаних страница",
+ "undeletepagetext": "{{PLURAL:$1|СледеÑ\9bа Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е избÑ\80иÑ\81ана, али Ñ\98е Ñ\98оÑ\88 Ñ\83 аÑ\80Ñ\85иви и може биÑ\82и вÑ\80аÑ\9bена|СледеÑ\9bе $1 Ñ\81Ñ\82Ñ\80аниÑ\86е Ñ\81Ñ\83 избÑ\80иÑ\81ане, али Ñ\81Ñ\83 Ñ\98оÑ\88 Ñ\83 аÑ\80Ñ\85иви и могÑ\83 биÑ\82и вÑ\80аÑ\9bене|СледеÑ\9bиÑ\85 $1 Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е избрисано, али су још у архиви и могу бити враћене}}.\nАрхива се повремено чисти од оваквих страница.",
"undelete-fieldset-title": "Враћање ревизија",
"undeleteextrahelp": "Да бисте вратили целу историју странице, оставите све кућице неозначене и кликните на дугме <strong><em>{{int:undeletebtn}}</em></strong>.\nАко желите да вратите одређене ревизије, означите их и кликните на <strong><em>{{int:undeletebtn}}</em></strong>.",
- "undeleterevisions": "{{PLURAL:$1|Ð\98змена}} обÑ\80иÑ\81ано: $1",
+ "undeleterevisions": "{{PLURAL:$1|Ð\98збÑ\80иÑ\81ана Ñ\98е|Ð\98збÑ\80иÑ\81ане Ñ\81Ñ\83|Ð\98збÑ\80иÑ\81ано Ñ\98е}} $1 {{PLURAL:$1|Ñ\80евизиÑ\98а|Ñ\80евизиÑ\98е|Ñ\80евизиÑ\98а}}",
"undeletehistory": "Ако вратите страницу, све ревизије ће бити враћене њеној историји.\nАко је у међувремену направљена нова страница с истим називом, враћене ревизије ће се појавити у њеној ранијој историји.",
- "undeleterevdel": "Ð\92Ñ\80аÑ\9bаÑ\9aе неÑ\9bе биÑ\82и извÑ\80Ñ\88ено ако Ñ\98е Ñ\80езÑ\83лÑ\82аÑ\82 Ñ\82ога делимиÑ\87но бÑ\80иÑ\81аÑ\9aе поÑ\81ледÑ\9aе Ñ\80евизиÑ\98е.\nУ Ñ\82аквим Ñ\81лÑ\83Ñ\87аÑ\98евима моÑ\80аÑ\82е иÑ\81кÑ\99Ñ\83Ñ\87иÑ\82и или оÑ\82кÑ\80иÑ\82и наÑ\98новиÑ\98е обрисане ревизије.",
- "undeletehistorynoadmin": "Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е обÑ\80иÑ\81ана.\nРазлог за бÑ\80иÑ\81аÑ\9aе Ñ\81е налази иÑ\81под, заÑ\98едно Ñ\81 деÑ\82аÑ\99има о коÑ\80иÑ\81никÑ\83 коÑ\98и Ñ\98е Ñ\83Ñ\80едио овÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 пÑ\80е бÑ\80иÑ\81аÑ\9aа.\nТекÑ\81Ñ\82 обрисаних ревизија је доступан само администраторима.",
- "undelete-revision": "Ð\9eбÑ\80иÑ\81ана измена Ñ\81Ñ\82Ñ\80аниÑ\86е $1 (дана $4; $5) од Ñ\81Ñ\82Ñ\80ане {{GENDER:$3|коÑ\80иÑ\81ника|коÑ\80иÑ\81ниÑ\86е|коÑ\80иÑ\81ника}} $3:",
- "undeleterevision-missing": "Ð\9dеважеÑ\9bа или недоÑ\81Ñ\82аÑ\98Ñ\83Ñ\9bа Ñ\80евизиÑ\98а.\nÐ\9cожда Ñ\81Ñ\82е Ñ\83нели погÑ\80еÑ\88нÑ\83 везÑ\83 или је ревизија враћена или уклоњена из архиве.",
+ "undeleterevdel": "Ð\92Ñ\80аÑ\9bаÑ\9aе неÑ\9bе биÑ\82и извÑ\80Ñ\88ено ако Ñ\98е Ñ\80езÑ\83лÑ\82аÑ\82 Ñ\82ога делимиÑ\87но бÑ\80иÑ\81аÑ\9aе поÑ\81ледÑ\9aе Ñ\80евизиÑ\98е.\nУ Ñ\82аквим Ñ\81лÑ\83Ñ\87аÑ\98евима моÑ\80аÑ\82е иÑ\81кÑ\99Ñ\83Ñ\87иÑ\82и или оÑ\82кÑ\80иÑ\82и наÑ\98новиÑ\98е избрисане ревизије.",
+ "undeletehistorynoadmin": "Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е избÑ\80иÑ\81ана.\nРазлог за бÑ\80иÑ\81аÑ\9aе Ñ\81е налази иÑ\81под, заÑ\98едно Ñ\81а деÑ\82аÑ\99има о коÑ\80иÑ\81никÑ\83 коÑ\98и Ñ\98е Ñ\83Ñ\80едио овÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 пÑ\80е бÑ\80иÑ\81аÑ\9aа.\nТекÑ\81Ñ\82 избрисаних ревизија је доступан само администраторима.",
+ "undelete-revision": "Ð\98збÑ\80иÑ\81ана Ñ\80евизиÑ\98а Ñ\81Ñ\82Ñ\80аниÑ\86е $1 (дана $4; $5) од Ñ\81Ñ\82Ñ\80ане {{GENDER:$3|коÑ\80иÑ\81ника|коÑ\80иÑ\81ниÑ\86е}} $3:",
+ "undeleterevision-missing": "Ð\9dеважеÑ\9bа или недоÑ\81Ñ\82аÑ\98Ñ\83Ñ\9bа Ñ\80евизиÑ\98а.\nÐ\9cожда Ñ\81Ñ\82е Ñ\83нели лоÑ\88 линк или је ревизија враћена или уклоњена из архиве.",
"undeleterevision-duplicate-revid": "Не могу вратити {{PLURAL:$1|ревизију|$1 ревизије|$1 ревизија}} јер се {{PLURAL:$1|њен|њихов}} <code>rev_id</code> већ користи.",
"undelete-nodiff": "Претходне измене нису пронађене.",
"undeletebtn": "Врати",
"undeletecomment": "Разлог:",
"cannotundelete": "Враћање једне или свих није успело:\n$1",
"undeletedpage": "<strong>Страница $1 је враћена</strong>\n\nПогледајте [[Special:Log/delete|евиденцију брисања]] за записе о недавним брисањима и враћањима.",
- "undelete-header": "Ð\9fогледаÑ\98Ñ\82е [[Special:Log/delete|евиденÑ\86иÑ\98Ñ\83 бÑ\80иÑ\81аÑ\9aа]] за недавно обрисане странице.",
- "undelete-search-title": "Ð\9fÑ\80еÑ\82Ñ\80ага обрисаних страница",
- "undelete-search-box": "Ð\9fÑ\80еÑ\82Ñ\80ажи обÑ\80иÑ\81ане Ñ\81Ñ\82Ñ\80аниÑ\86е",
+ "undelete-header": "Ð\9fогледаÑ\98Ñ\82е [[Special:Log/delete|евиденÑ\86иÑ\98Ñ\83 бÑ\80иÑ\81аÑ\9aа]] за недавно избрисане странице.",
+ "undelete-search-title": "Ð\9fÑ\80еÑ\82Ñ\80ага избрисаних страница",
+ "undelete-search-box": "Ð\9fÑ\80еÑ\82Ñ\80ага избÑ\80иÑ\81аниÑ\85 Ñ\81Ñ\82Ñ\80аниÑ\86а",
"undelete-search-prefix": "Прикажи странице које почињу са:",
"undelete-search-full": "Прикажи наслове који садрже:",
"undelete-search-submit": "Претражи",
"undelete-bad-store-key": "Не могу да вратим измену датотеке од $1: датотека је недостајала пре брисања.",
"undelete-cleanup-error": "Грешка при брисању некоришћене архиве „$1“.",
"undelete-missing-filearchive": "Не могу да вратим архиву с ИБ $1 јер се она не налази у бази података.\nМожда је већ била враћена.",
- "undelete-error": "Ð\94оÑ\88ло Ñ\98е до гÑ\80еÑ\88ке пÑ\80и вÑ\80аÑ\9bаÑ\9aÑ\83 обрисане странице",
+ "undelete-error": "Ð\94оÑ\88ло Ñ\98е до гÑ\80еÑ\88ке пÑ\80и вÑ\80аÑ\9bаÑ\9aÑ\83 избрисане странице",
"undelete-error-short": "Грешка при враћању датотеке: $1",
"undelete-error-long": "Дошло је до грешке при враћању датотеке:\n\n$1",
- "undelete-show-file-confirm": "Ð\94а ли Ñ\81Ñ\82е Ñ\81игÑ\83Ñ\80ни да желиÑ\82е да погледаÑ\82е обÑ\80иÑ\81анÑ\83 измену датотеке „<nowiki>$1</nowiki>“ од $2 у $3?",
+ "undelete-show-file-confirm": "Ð\88еÑ\81Ñ\82е ли Ñ\81игÑ\83Ñ\80ни да желиÑ\82е да погледаÑ\82е избÑ\80иÑ\81анÑ\83 Ñ\80евизиÑ\98у датотеке „<nowiki>$1</nowiki>“ од $2 у $3?",
"undelete-show-file-submit": "Да",
"namespace": "Именски простор:",
"invert": "Обрни избор",
"tooltip-invert": "Означите ову кутијуцу да бисте сакрили промене на страницана у изабраном именском простору (и повезаним именским просторима, ако је означено)",
- "tooltip-whatlinkshere-invert": "Ð\9eзнаÑ\87иÑ\82е овÑ\83 кÑ\83Ñ\82иÑ\98иÑ\86Ñ\83 за Ñ\81акÑ\80иваÑ\9aе веза са страница у изабраном именском простору.",
+ "tooltip-whatlinkshere-invert": "Ð\9eзнаÑ\87иÑ\82е овÑ\83 кÑ\83Ñ\82иÑ\98иÑ\86Ñ\83 за Ñ\81акÑ\80иваÑ\9aе линкова са страница у изабраном именском простору.",
"namespace_association": "Повезани именски простор",
"tooltip-namespace_association": "Означите ову кутијицу да бисте укључили и разговор или именски простор теме која је повезана са изабраним именским простором",
"blanknamespace": "(главни)",
"sp-contributions-newbies-sub": "За нове кориснике",
"sp-contributions-newbies-title": "Доприноси нових корисника",
"sp-contributions-blocklog": "евиденција блокирања",
- "sp-contributions-suppresslog": "обрисани доприноси {{GENDER:$1|корисника|кориснице}}",
- "sp-contributions-deleted": "обрисани доприноси {{GENDER:$1|корисника|кориснице}}",
+ "sp-contributions-suppresslog": "избрисани доприноси {{GENDER:$1|корисника|кориснице}}",
+ "sp-contributions-deleted": "избрисани доприноси {{GENDER:$1|корисника|кориснице}}",
"sp-contributions-uploads": "отпремања",
"sp-contributions-logs": "евиденције",
"sp-contributions-talk": "разговор",
"sp-contributions-hideminor": "Сакриј мање измене",
"sp-contributions-submit": "Претражи",
"whatlinkshere": "Шта води овде",
- "whatlinkshere-title": "Странице које су повезане са „$1”",
+ "whatlinkshere-title": "Странице које воде на страницу „$1”",
"whatlinkshere-page": "Страница:",
- "linkshere": "СледеÑ\9bе Ñ\81Ñ\82Ñ\80аниÑ\86е имаÑ\98Ñ\83 везÑ\83 до <strong>$2</strong>:",
+ "linkshere": "СледеÑ\9bе Ñ\81Ñ\82Ñ\80аниÑ\86е воде на Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 <strong>$2</strong>:",
"nolinkshere": "Ниједна страница није повезана са: <strong>$2</strong>.",
- "nolinkshere-ns": "Ð\9dиÑ\98една Ñ\81Ñ\82Ñ\80аниÑ\86а не води до '''$2''' у изабраном именском простору.",
+ "nolinkshere-ns": "Ð\9dиÑ\98една Ñ\81Ñ\82Ñ\80аниÑ\86а не води на Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 <strong>$2</strong> у изабраном именском простору.",
"isredirect": "преусмерење",
"istemplate": "укључивање",
- "isimage": "веза до датотеке",
+ "isimage": "линк до датотеке",
"whatlinkshere-prev": "{{PLURAL:$1|претходни|претходна $1|претходних $1}}",
"whatlinkshere-next": "{{PLURAL:$1|следећи|следећа $1|следећих $1}}",
- "whatlinkshere-links": "â\86\90 везе",
+ "whatlinkshere-links": "â\86\90 линкови",
"whatlinkshere-hideredirs": "$1 преусмерења",
"whatlinkshere-hidetrans": "$1 укључивања",
- "whatlinkshere-hidelinks": "$1 везе",
- "whatlinkshere-hideimages": "$1 везе до датотеке",
+ "whatlinkshere-hidelinks": "$1 линкове",
+ "whatlinkshere-hideimages": "$1 линкова до датотеке",
"whatlinkshere-filters": "Филтери",
"whatlinkshere-submit": "Иди",
"autoblockid": "Аутоматско блокирање #$1",
"ipaddressorusername": "IP адреса или корисничко име:",
"ipbexpiry": "Истиче:",
"ipbreason": "Разлог:",
- "ipbreason-dropdown": "*Ð\9dаÑ\98Ñ\87еÑ\88Ñ\9bи Ñ\80азлози за блокиÑ\80аÑ\9aе\n** УноÑ\88еÑ\9aе лажниÑ\85 инÑ\84оÑ\80маÑ\86иÑ\98а\n** УклаÑ\9aаÑ\9aе Ñ\81адÑ\80жаÑ\98а Ñ\81а Ñ\81Ñ\82Ñ\80аниÑ\86а\n** Ð\9fоÑ\81Ñ\82авÑ\99аÑ\9aе веза до Ñ\81поÑ\99аÑ\88Ñ\9aиÑ\85 Ñ\81аÑ\98Ñ\82ова\n** УноÑ\88еÑ\9aе беÑ\81миÑ\81лиÑ\86а у странице\n** Непристојно понашање\n** Употреба више налога\n** Неприхватљиво корисничко име",
+ "ipbreason-dropdown": "*Ð\9dаÑ\98Ñ\87еÑ\88Ñ\9bи Ñ\80азлози за блокиÑ\80аÑ\9aе\n** УмеÑ\82аÑ\9aе лажниÑ\85 инÑ\84оÑ\80маÑ\86иÑ\98а\n** УклаÑ\9aаÑ\9aе Ñ\81адÑ\80жаÑ\98а Ñ\81а Ñ\81Ñ\82Ñ\80аниÑ\86а\n** Ð\94одаваÑ\9aе непожеÑ\99ниÑ\85 линкова до Ñ\81поÑ\99аÑ\88Ñ\9aиÑ\85 Ñ\81аÑ\98Ñ\82ова\n** УноÑ\88еÑ\9aе беÑ\81миÑ\81лиÑ\86а/гÑ\80аÑ\84иÑ\82а у странице\n** Непристојно понашање\n** Употреба више налога\n** Неприхватљиво корисничко име",
"ipb-hardblock": "Спречи пријављене кориснике да уређују с ове IP адресе",
"ipbcreateaccount": "Онемогући отварање налога",
"ipbemailban": "Спречи корисника да шаље имејлове",
"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}}“ на дну странице.",
"lockedbyandtime": "(од $1 дана $2 у $3)",
"move-page": "Премештање „$1”",
"move-page-legend": "Премештање странице",
- "movepagetext": "Ð\94оÑ\9aи обÑ\80азаÑ\86 Ñ\9bе пÑ\80еименоваÑ\82и Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, пÑ\80емеÑ\88Ñ\82аÑ\98Ñ\83Ñ\9bи Ñ\86елÑ\83 иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 на ново име.\nСÑ\82аÑ\80и наÑ\81лов поÑ\81Ñ\82аÑ\9bе пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aе на нови.\nÐ\9cожеÑ\82е ажÑ\83Ñ\80иÑ\80аÑ\82и пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа коÑ\98а воде до извоÑ\80ног наÑ\81лова;\nпогледаÑ\98Ñ\82е [[Special:DoubleRedirects|двоÑ\81Ñ\82Ñ\80Ñ\83ка]] или [[Special:BrokenRedirects|покваÑ\80ена]] пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа.\nÐ\9dа вама Ñ\98е одговоÑ\80ноÑ\81Ñ\82 да везе и даље иду тамо где треба.\n\nСтраница <strong>неће</strong> бити премештена ако већ постоји страница с тим именом (осим ако је празна, садржи преусмерење или нема историју измена).\nТо значи да можете вратити страницу на претходно име ако погрешите, али не можете ''преписати'' постојећу.\n\n<strong>Напомена:</strong>\nОво може представљати драстичну и неочекивану измену за популарну страницу;\nдобро размислите о последицама пре него што наставите.",
- "movepagetext-noredirectfixer": "Ð\94оÑ\9aи обÑ\80азаÑ\86 Ñ\9bе пÑ\80еименоваÑ\82и Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, пÑ\80емеÑ\88Ñ\82аÑ\98Ñ\83Ñ\9bи Ñ\86елÑ\83 иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 на ново име.\nСÑ\82аÑ\80и наÑ\81лов поÑ\81Ñ\82аÑ\9bе пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aе на нови.\nÐ\9fогледаÑ\98Ñ\82е [[Special:DoubleRedirects|двоÑ\81Ñ\82Ñ\80Ñ\83ка]] или [[Special:BrokenRedirects|покваÑ\80ена]] пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа.\nÐ\9dа вама Ñ\98е одговоÑ\80ноÑ\81Ñ\82 да везе и даље иду тамо где треба.\n\nСтраница <strong>неће</strong> бити премештена ако већ постоји страница с тим именом (осим ако је празна, садржи преусмерење или нема историју измена).\nТо значи да можете вратити страницу на претходно име ако погрешите, али не можете ''преписати'' постојећу.\n\n<strong>Напомена:</strong>\nОво може представљати драстичну и неочекивану измену за популарну страницу;\nдобро размислите о последицама пре него што наставите.",
+ "movepagetext": "Ð\94оÑ\9aи обÑ\80азаÑ\86 Ñ\9bе пÑ\80еименоваÑ\82и Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, пÑ\80емеÑ\88Ñ\82аÑ\98Ñ\83Ñ\9bи Ñ\86елÑ\83 иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 на ново име.\nСÑ\82аÑ\80и наÑ\81лов поÑ\81Ñ\82аÑ\9bе пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aе на нови.\nÐ\9cожеÑ\82е ажÑ\83Ñ\80иÑ\80аÑ\82и пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа коÑ\98а воде до извоÑ\80ног наÑ\81лова;\nпогледаÑ\98Ñ\82е [[Special:DoubleRedirects|двоÑ\81Ñ\82Ñ\80Ñ\83ка]] или [[Special:BrokenRedirects|покваÑ\80ена]] пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа.\nÐ\9dа вама Ñ\98е одговоÑ\80ноÑ\81Ñ\82 да линкови и даље иду тамо где треба.\n\nСтраница <strong>неће</strong> бити премештена ако већ постоји страница с тим именом (осим ако је празна, садржи преусмерење или нема историју измена).\nТо значи да можете вратити страницу на претходно име ако погрешите, али не можете ''преписати'' постојећу.\n\n<strong>Напомена:</strong>\nОво може представљати драстичну и неочекивану измену за популарну страницу;\nдобро размислите о последицама пре него што наставите.",
+ "movepagetext-noredirectfixer": "Ð\94оÑ\9aи обÑ\80азаÑ\86 Ñ\9bе пÑ\80еименоваÑ\82и Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, пÑ\80емеÑ\88Ñ\82аÑ\98Ñ\83Ñ\9bи Ñ\86елÑ\83 иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 на ново име.\nСÑ\82аÑ\80и наÑ\81лов поÑ\81Ñ\82аÑ\9bе пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aе на нови.\nÐ\9fогледаÑ\98Ñ\82е [[Special:DoubleRedirects|двоÑ\81Ñ\82Ñ\80Ñ\83ка]] или [[Special:BrokenRedirects|покваÑ\80ена]] пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа.\nÐ\9dа вама Ñ\98е одговоÑ\80ноÑ\81Ñ\82 да линкови и даље иду тамо где треба.\n\nСтраница <strong>неће</strong> бити премештена ако већ постоји страница с тим именом (осим ако је празна, садржи преусмерење или нема историју измена).\nТо значи да можете вратити страницу на претходно име ако погрешите, али не можете ''преписати'' постојећу.\n\n<strong>Напомена:</strong>\nОво може представљати драстичну и неочекивану измену за популарну страницу;\nдобро размислите о последицама пре него што наставите.",
"movepagetalktext": "Ако сте означили овај квадратић, одговарајућа страница за разговор биће аутоматски премештена на нови наслов, осим ако већ постоји страница за разговор са истим насловом.\n\nУ том случају, мораћете ручно да је преместите или спојите, ако има потребе за тим.",
"moveuserpage-warning": "'''Упозорење:''' на путу сте да преместите корисничку страницу. Имајте у виду да ће само страница бити премештена, а сам корисник ''неће'' бити преименован.",
"movecategorypage-warning": "<strong>Упозорење:</strong> премештате страницу категорије. Имајте на уму да ће само страница бити премештена и да све странице у старој категорији <em>неће</em> бити рекатегорисане у нову категорију.",
"movepage-moved": "'''„$1“ је премештена на „$2“'''",
"movepage-moved-redirect": "Преусмерење је направљено.",
"movepage-moved-noredirect": "Стварање преусмерења је онемогућено.",
- "articleexists": "Страница с тим именом већ постоји или име које сте одабрали није важеће.\nОдаберите друго.",
- "cantmove-titleprotected": "Не можете да преместите страницу на то место јер је жељени наслов заштићен од стварања",
+ "articleexists": "Страница са тим именом већ постоји или име које сте одабрали није важеће.\nОдаберите друго.",
+ "cantmove-titleprotected": "Не можете да преместите страницу на ову локацију јер је прављење новог наслова заштићено.",
"movetalk": "Премести и страницу за разговор",
"move-subpages": "Премести и подстранице (до $1)",
"move-talk-subpages": "Премести подстранице странице за разговор (до $1)",
"movenosubpage": "Ова страница нема подстрана.",
"movereason": "Разлог:",
"revertmove": "врати",
- "delete_and_move_text": "Ð\9eдÑ\80едиÑ\88на Ñ\81Ñ\82Ñ\80аниÑ\86а â\80\9e[[:$1]]â\80\9c веÑ\9b поÑ\81Ñ\82оÑ\98и. \nÐ\96елиÑ\82е ли да Ñ\98е обришете да бисте ослободили место за премештање?",
- "delete_and_move_confirm": "Ð\94а, обриши страницу",
- "delete_and_move_reason": "Ð\9eбрисано да се ослободи место за премештање из „[[$1]]“",
+ "delete_and_move_text": "Ð\9eдÑ\80едиÑ\88на Ñ\81Ñ\82Ñ\80аниÑ\86а â\80\9e[[:$1]]â\80\9c веÑ\9b поÑ\81Ñ\82оÑ\98и. \nÐ\96елиÑ\82е ли да Ñ\98е избришете да бисте ослободили место за премештање?",
+ "delete_and_move_confirm": "Ð\94а, избриши страницу",
+ "delete_and_move_reason": "Ð\98збрисано да се ослободи место за премештање из „[[$1]]“",
"selfmove": "Наслов је истоветан;\nне можете преместити страницу преко саме себе.",
"immobile-source-namespace": "Не могу преместити странице у именски простор „$1“.",
"immobile-target-namespace": "Не могу преместити странице у именски простор „$1“.",
- "immobile-target-namespace-iw": "Ð\9cеÑ\92Ñ\83вики веза ниÑ\98е валидно одредиште за премештање странице.",
+ "immobile-target-namespace-iw": "Ð\9cеÑ\92Ñ\83вики линк ниÑ\98е важеÑ\9bе одредиште за премештање странице.",
"immobile-source-page": "Ова страница се не може преместити.",
"immobile-target-page": "Не могу да преместим на жељени наслов.",
"bad-target-model": "Жељено одредиште користи другачији модел садржаја. Не могу да претворим из $1 у $2.",
"move-over-sharedrepo": "[[:$1]] се налази на дељеном складишту. Ако преместите датотеку на овај наслов, то ће заменити дељену датотеку.",
"file-exists-sharedrepo": "Наведени назив датотеке се већ користи у дељеном складишту.\nИзаберите други назив.",
"export": "Извоз страница",
- "exporttext": "Ð\9cожеÑ\82е да извезеÑ\82е Ñ\82екÑ\81Ñ\82 и иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 измена одÑ\80еÑ\92ене Ñ\81Ñ\82Ñ\80аниÑ\86е или Ñ\81кÑ\83па Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\83клÑ\99ениÑ\85 Ñ\83 XML Ñ\84оÑ\80маÑ\82Ñ\83.\nÐ\9eво онда може да бÑ\83де Ñ\83везено Ñ\83 дÑ\80Ñ\83ги вики коÑ\98и коÑ\80иÑ\81Ñ\82и Ð\9cедиÑ\98авики Ñ\81оÑ\84Ñ\82веÑ\80 пÑ\80еко [[Special:Import|Ñ\81Ñ\82Ñ\80аниÑ\86е за Ñ\83воз]].\n\nÐ\94а биÑ\81Ñ\82е извезли Ñ\81Ñ\82Ñ\80аниÑ\86е, Ñ\83неÑ\81иÑ\82е називе Ñ\83 оквиÑ\80Ñ\83 иÑ\81под, Ñ\81 Ñ\98едним наÑ\81ловом по Ñ\80едÑ\83, и изабеÑ\80иÑ\82е да ли желиÑ\82е акÑ\82Ñ\83елнÑ\83 Ñ\80евизиÑ\98Ñ\83 и Ñ\81ве оÑ\81Ñ\82але, или Ñ\81амо акÑ\82Ñ\83елнÑ\83 Ñ\80евизиÑ\98Ñ\83 Ñ\81 подаÑ\86има о поÑ\81ледÑ\9aоÑ\98 измени.\n\nУ дÑ\80Ñ\83гом Ñ\81лÑ\83Ñ\87аÑ\98Ñ\83, можеÑ\82е коÑ\80иÑ\81Ñ\82иÑ\82и и везÑ\83, на пример [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] за страницу [[{{MediaWiki:Mainpage}}]].",
+ "exporttext": "Ð\9cожеÑ\82е да извезеÑ\82е Ñ\82екÑ\81Ñ\82 и иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 измена одÑ\80еÑ\92ене Ñ\81Ñ\82Ñ\80аниÑ\86е или Ñ\81кÑ\83па Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\83клÑ\99ениÑ\85 Ñ\83 XML Ñ\84оÑ\80маÑ\82Ñ\83.\nÐ\9eво онда може да бÑ\83де Ñ\83везено Ñ\83 дÑ\80Ñ\83ги вики коÑ\98и коÑ\80иÑ\81Ñ\82и Ð\9cедиÑ\98авики Ñ\81оÑ\84Ñ\82веÑ\80 пÑ\80еко [[Special:Import|Ñ\81Ñ\82Ñ\80аниÑ\86е за Ñ\83воз]].\n\nÐ\94а биÑ\81Ñ\82е извезли Ñ\81Ñ\82Ñ\80аниÑ\86е, Ñ\83неÑ\81иÑ\82е називе Ñ\83 оквиÑ\80Ñ\83 иÑ\81под, Ñ\81 Ñ\98едним наÑ\81ловом по Ñ\80едÑ\83, и изабеÑ\80иÑ\82е да ли желиÑ\82е акÑ\82Ñ\83елнÑ\83 Ñ\80евизиÑ\98Ñ\83 и Ñ\81ве оÑ\81Ñ\82але, или Ñ\81амо акÑ\82Ñ\83елнÑ\83 Ñ\80евизиÑ\98Ñ\83 Ñ\81 подаÑ\86има о поÑ\81ледÑ\9aоÑ\98 измени.\n\nУ дÑ\80Ñ\83гом Ñ\81лÑ\83Ñ\87аÑ\98Ñ\83, можеÑ\82е коÑ\80иÑ\81Ñ\82иÑ\82и и линк, на пример [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] за страницу [[{{MediaWiki:Mainpage}}]].",
"exportall": "Извези све странице",
"exportcuronly": "Укључи само актуелну ревизију, не целу историју",
"exportnohistory": "----\n'''Напомена:''' извоз пуне историје страница преко овог обрасца је онемогућено из техничких разлога.",
"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": "Ð\9dеиÑ\81пÑ\80авни паÑ\80амеÑ\82Ñ\80и за миниÑ\98аÑ\82Ñ\83Ñ\80Ñ\83",
+ "thumbnail_invalid_params": "Ð\9dеважеÑ\9bи паÑ\80амеÑ\82Ñ\80и Ñ\81лиÑ\87иÑ\86е",
"thumbnail_toobigimagearea": "Датотека са величинама већим од $1",
"thumbnail_dest_directory": "Не могу да направим одредишну фасциклу",
"thumbnail_image-type": "Тип слике није подржан",
"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|евиденцији увоза]].",
"import-upload-filename": "Назив датотеке:",
"import-upload-username-prefix": "Међувики префикс:",
"import-comment": "Коментар:",
- "importtext": "Извезите датотеку с изворног викија користећи [[Special:Export|извоз]].\nСачувајте је на рачунар и пошаљите овде.",
+ "importtext": "Извезите датотеку сa изворног викија користећи [[Special:Export|алат за извоз]].\nСачувајте је на рачунар и оптремите овде.",
"importstart": "Увозим странице…",
"import-revision-count": "$1 {{PLURAL:$1|ревизија|ревизије|ревизија}}",
"importnopages": "Нема страница за увоз.",
"importunknownsource": "Непознат изворни тип увоза",
"importnoprefix": "Није наведен међувики префикс",
"importcantopen": "Не могу да отворим датотеку за увоз.",
- "importbadinterwiki": "Ð\9dеиÑ\81пÑ\80авна меÑ\92Ñ\83вики веза",
+ "importbadinterwiki": "Ð\9bоÑ\88 меÑ\92Ñ\83вики линк",
"importsuccess": "Увожење је завршено!",
"importnosources": "Није одређен ниједан извор за увоз, тако да је отпремање историје онемогућено.",
"importnofile": "Увозна датотека није послата.",
"import-error-create": "Страница „$1“ није увезена јер вам није дозвољено да је направите.",
"import-error-interwiki": "Не могу да увезем страницу „$1“ јер је њен назив резервисан за спољно повезивање (међувики).",
"import-error-special": "Не могу да увезем страницу „$1“ јер она припада посебном именском простору које не прихвата странице.",
- "import-error-invalid": "Ð\9dе могÑ\83 да Ñ\83везем Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 â\80\9e$1â\80\9c Ñ\98еÑ\80 Ñ\98е Ñ\9aен назив неиÑ\81пÑ\80аван.",
+ "import-error-invalid": "СÑ\82Ñ\80аниÑ\86а â\80\9e$1â\80\9c ниÑ\98е Ñ\83везена Ñ\98еÑ\80 Ñ\98е име под коÑ\98им Ñ\81е Ñ\82Ñ\80еба Ñ\83воÑ\81Ñ\82и неважеÑ\9bе на овом викиÑ\98Ñ\83.",
"import-error-unserialize": "Не могу да десеријализујем ревизију $2 странице $1. Записано је да ревизија користи $3 модел садржаја у $4 формату.",
"import-options-wrong": "{{PLURAL:$2|Погрешна опција|Погрешне опције}}: <nowiki>$1</nowiki>",
- "import-rootpage-invalid": "Ð\9dаведена оÑ\81новна Ñ\81Ñ\82Ñ\80аниÑ\86а има неиÑ\81пÑ\80аван наслов.",
+ "import-rootpage-invalid": "Ð\9dаведена оÑ\81новна Ñ\81Ñ\82Ñ\80аниÑ\86а има неважеÑ\9bи наслов.",
"import-rootpage-nosubpage": "Именски простор „$1“ основне странице не дозвољава подстранице.",
"importlogpage": "Евиденција увоза",
"importlogpagetext": "Административни увози страница с историјама измена с других викија.",
"tooltip-ca-history": "Претходне ревизије ове странице",
"tooltip-ca-protect": "Заштитите ову страницу",
"tooltip-ca-unprotect": "Промени заштиту ове странице",
- "tooltip-ca-delete": "Ð\9eбришите ову страницу",
- "tooltip-ca-undelete": "Ð\92Ñ\80аÑ\82и измене напÑ\80авÑ\99ене на овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и пÑ\80е него Ñ\88Ñ\82о бÑ\83де обÑ\80иÑ\81ана",
+ "tooltip-ca-delete": "Ð\98збришите ову страницу",
+ "tooltip-ca-undelete": "Ð\92Ñ\80аÑ\82и измене коÑ\98е Ñ\81Ñ\83 наÑ\87иÑ\9aене на овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и пÑ\80е бÑ\80иÑ\81аÑ\9aа Ñ\81Ñ\82Ñ\80аниÑ\86е",
"tooltip-ca-move": "Премести ову страницу",
"tooltip-ca-watch": "Додајте ову страницу на свој списак надгледања",
"tooltip-ca-unwatch": "Уклоните ову страницу са списка надгледања",
"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 фид за ову страницу",
"tooltip-t-upload": "Отпремите датотеке",
"tooltip-t-specialpages": "Списак свих посебних страница",
"tooltip-t-print": "Верзија ове странице за штампање",
- "tooltip-t-permalink": "ТÑ\80аÑ\98на веза ка овој ревизији странице",
+ "tooltip-t-permalink": "ТÑ\80аÑ\98ни линк ка овој ревизији странице",
"tooltip-ca-nstab-main": "Погледајте страницу са садржајем",
"tooltip-ca-nstab-user": "Погледајте корисничку страницу",
"tooltip-ca-nstab-media": "Погледајте медијску страницу",
"tooltip-minoredit": "Означите ову измену као мању",
"tooltip-save": "Сачувајте своје промене",
"tooltip-publish": "Објавите своје измене",
- "tooltip-preview": "Ð\9fÑ\80егледаÑ\98Ñ\82е Ñ\81воÑ\98е измене. Користите ово дугме пре чувања.",
+ "tooltip-preview": "Ð\9fÑ\80егледаÑ\98Ñ\82е Ñ\81воÑ\98е пÑ\80омене. Користите ово дугме пре чувања.",
"tooltip-diff": "Погледајте које промене сте направили на тексту",
"tooltip-compareselectedversions": "Погледаjте разлике између две изабране ревизије ове странице",
"tooltip-watch": "Додајте ову страницу на свој списак надгледања",
"tooltip-watchlistedit-normal-submit": "Уклоните наслове",
"tooltip-watchlistedit-raw-submit": "Ажурирај списак",
- "tooltip-recreate": "Ð\9fоново напÑ\80авиÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 иако Ñ\98е обрисана",
+ "tooltip-recreate": "Ð\9fоново напÑ\80авиÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 иако Ñ\98е веÑ\9b избрисана",
"tooltip-upload": "Започните отпремање",
"tooltip-rollback": "„Врати“ враћа измене последњег доприносиоца ове странице једним кликом",
"tooltip-undo": "„Поништи” враћа ову измену и отвара образац за уређивање у претпрегледном моду. Дозвољава додавање разлога у резимеу.",
"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",
"creditspage": "Аутори странице",
"nocredits": "Не постоје подаци о аутору ове странице.",
"spamprotectiontitle": "Филтер за заштиту од непожељних порука",
- "spamprotectiontext": "ФилÑ\82еÑ\80а пÑ\80оÑ\82ив нежеÑ\99ениÑ\85 поÑ\80Ñ\83ка Ñ\98е блокиÑ\80ао Ñ\87Ñ\83ваÑ\9aе ове Ñ\81Ñ\82Ñ\80аниÑ\86е.\nÐ\9eво Ñ\98е веÑ\80оваÑ\82но изазвано везом до спољашњег сајта који се налази на црном списку.",
+ "spamprotectiontext": "ФилÑ\82еÑ\80а пÑ\80оÑ\82ив нежеÑ\99ениÑ\85 поÑ\80Ñ\83ка Ñ\98е блокиÑ\80ао Ñ\87Ñ\83ваÑ\9aе ове Ñ\81Ñ\82Ñ\80аниÑ\86е.\nÐ\9eво Ñ\98е веÑ\80оваÑ\82но изазвано линком до спољашњег сајта који се налази на црном списку.",
"spamprotectionmatch": "Следећи текст је активирао наш филтер за нежељене поруке: $1",
"spambot_username": "Чишћење непожељних порука у Медијавикији",
- "spam_reverting": "Ð\92Ñ\80аÑ\9bам на поÑ\81ледÑ\9aÑ\83 Ñ\80евизиÑ\98Ñ\83 коÑ\98а не Ñ\81адÑ\80жи везе до $1",
- "spam_blanking": "Све измене садрже везе до $1. Чистим",
- "spam_deleting": "Све Ñ\80евизиÑ\98е Ñ\81адÑ\80же везе до $1. Бришем",
+ "spam_reverting": "Ð\92Ñ\80аÑ\9bам на поÑ\81ледÑ\9aÑ\83 Ñ\80евизиÑ\98Ñ\83 коÑ\98а не Ñ\81адÑ\80жи линкове до $1",
+ "spam_blanking": "Све ревизије садрже линкове до $1. Празним",
+ "spam_deleting": "Све Ñ\80евизиÑ\98е Ñ\81адÑ\80же линкове до $1. Бришем",
"simpleantispam-label": "Провера против нежељеног садржаја. \n<strong>Не</strong> попуњавајте ово!",
"pageinfo-title": "Информације за „$1“",
"pageinfo-not-current": "Нажалост, немогуће је навести ове инфомације за старије ревизије.",
"markaspatrolledtext": "Означи страницу као патролирану",
"markaspatrolledtext-file": "Означи ову верзију датотеке као патролирану",
"markedaspatrolled": "Означено као патролирано",
- "markedaspatrolledtext": "Изабрана ревизија странице [[:$1]] је означена као патролирана.",
+ "markedaspatrolledtext": "Изабрана ревизија странице [[:$1]] означена је као патролирана.",
"rcpatroldisabled": "Патролирање скорашњих измена је онемогућено",
"rcpatroldisabledtext": "Могућност патролирања скорашњих измена је актуелно онемогућена.",
"markedaspatrollederror": "Не могу да означим као патролирано.",
"patrol-log-header": "Ово је евиденција патролираних ревизија.",
"confirm-markpatrolled-button": "У реду",
"confirm-markpatrolled-top": "Означити ревизију $3 странице $2 као патролирану?",
- "deletedrevision": "Ð\9eбрисана стара ревизија $1.",
+ "deletedrevision": "Ð\98збрисана стара ревизија $1.",
"filedeleteerror-short": "Грешка при брисању датотеке: $1",
"filedeleteerror-long": "Дошло је до грешака при брисању датотеке:\n\n$1",
- "filedelete-missing": "Ð\94аÑ\82оÑ\82ека â\80\9e$1â\80\9c Ñ\81е не може обÑ\80иÑ\81аÑ\82и јер не постоји.",
+ "filedelete-missing": "Ð\9dе могÑ\83 да избÑ\80иÑ\88ем даÑ\82оÑ\82екÑ\83 â\80\9e$1â\80\9c јер не постоји.",
"filedelete-old-unregistered": "Наведена ревизија датотеке „$1“ не постоји у бази података.",
"filedelete-current-unregistered": "Наведена датотека „$1“ не постоји у бази података.",
"filedelete-archive-read-only": "Сервер не може да пише по складишној фасцикли ($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-nohires": "Већа резолуција није доступна.",
"svg-long-desc": "SVG датотека, номинално $1 × $2 пиксела, величина: $3",
"svg-long-desc-animated": "Анимирана SVG датотека, номинално: $1 × $2 пиксела, величина: $3",
- "svg-long-error": "Ð\9dеиÑ\81пÑ\80авна SVG датотека: $1",
+ "svg-long-error": "Ð\9dеважеÑ\9bа SVG датотека: $1",
"show-big-image": "Првобитна датотека",
"show-big-image-preview": "Величина овог приказа: $1.",
"show-big-image-preview-differ": "Величина $3 прегледа за ову $2 датотеку је $1.",
"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-label": "Назив датотеке (или њен део):",
"newimages-user": "IP адреса или корисничко име",
"newimages-newbies": "Прикажи само доприносе нових налога",
- "newimages-showbots": "Ð\9fÑ\80икажи даÑ\82оÑ\82еке коÑ\98е Ñ\81Ñ\83 поÑ\81лали боÑ\82ови",
+ "newimages-showbots": "Ð\9fÑ\80икажи оÑ\82пÑ\80емаÑ\9aа боÑ\82ова",
"newimages-hidepatrolled": "Сакриј патролирана отпремања",
"newimages-mediatype": "Тип датотеке:",
"noimages": "Нема ништа.",
- "gallery-slideshow-toggle": "минијатуре",
+ "gallery-slideshow-toggle": "сличице",
"ilsubmit": "Претражи",
"bydate": "по датуму",
"sp-newimages-showfrom": "прикажи нове датотеке почевши од $1, $2",
"saturday-at": "у суботу у $1",
"sunday-at": "у недељу у $1",
"yesterday-at": "Јуче у $1",
- "bad_image_list": "ФоÑ\80маÑ\82 Ñ\98е Ñ\81ледеÑ\9bи:\n\nРазмаÑ\82Ñ\80аÑ\98Ñ\83 Ñ\81е Ñ\81амо набÑ\80аÑ\98аÑ\9aа (Ñ\80едови коÑ\98и поÑ\87иÑ\9aÑ\83 Ñ\81а звездиÑ\86ом).\nÐ\9fÑ\80ва веза Ñ\83 Ñ\80едÑ\83 моÑ\80а да бÑ\83де веза до неиÑ\81пÑ\80авне даÑ\82оÑ\82еке.\nСве даÑ\99Ñ\9aе везе у истом реду сматрају се изузецима.",
+ "bad_image_list": "ФоÑ\80маÑ\82 Ñ\98е Ñ\81ледеÑ\9bи:\n\nРазмаÑ\82Ñ\80аÑ\98Ñ\83 Ñ\81е Ñ\81амо набÑ\80аÑ\98аÑ\9aа (Ñ\80едови коÑ\98и поÑ\87иÑ\9aÑ\83 Ñ\81а звездиÑ\86ом).\nÐ\9fÑ\80ви линк Ñ\83 Ñ\80едÑ\83 моÑ\80а да бÑ\83де линк до неиÑ\81пÑ\80авне даÑ\82оÑ\82еке.\nСви даÑ\99Ñ\9aи линкови у истом реду сматрају се изузецима.",
"variantname-zh-hans": "hans",
"variantname-zh-hant": "hant",
"variantname-zh-cn": "cn",
"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": "Писац",
"exif-originaldocumentid": "Јединствени ID изворног документа",
"exif-licenseurl": "Адреса лиценце за ауторска права",
"exif-morepermissionsurl": "Резервни подаци о лиценцирању",
- "exif-attributionurl": "Ð\9fÑ\80и поновном коÑ\80иÑ\88Ñ\9bеÑ\9aÑ\83 овог Ñ\80ада, коÑ\80иÑ\81Ñ\82иÑ\82е везÑ\83 до",
+ "exif-attributionurl": "Ð\9fÑ\80и поновном коÑ\80иÑ\88Ñ\9bеÑ\9aÑ\83 овог Ñ\80ада, коÑ\80иÑ\81Ñ\82иÑ\82е линк до",
"exif-preferredattributionname": "При поновном коришћењу овог рада, поставите заслуге",
"exif-pngfilecomment": "Коментар на датотеку PNG",
"exif-disclaimer": "Одрицање одговорности",
"exif-urgency-other": "Прилагођени приоритет ($1)",
"namespacesall": "сви",
"monthsall": "све",
- "confirmemail": "Потврда имејл адресе",
- "confirmemail_noemail": "Нисте унели валидну имејл адресу у [[Special:Preferences|подешавањима]].",
- "confirmemail_text": "{{SITENAME}} заÑ\85Ñ\82ева да поÑ\82вÑ\80диÑ\82е имеÑ\98л адÑ\80еÑ\81Ñ\83 пÑ\80е него Ñ\88Ñ\82о поÑ\87неÑ\82е да коÑ\80иÑ\81Ñ\82иÑ\82е могÑ\83Ñ\9bноÑ\81Ñ\82и имеÑ\98ла.\nÐ\9aликниÑ\82е на дÑ\83гме иÑ\81под за Ñ\81лаÑ\9aе поÑ\80Ñ\83ке на ваÑ\88Ñ\83 адÑ\80еÑ\81Ñ\83.\nУ поÑ\80Ñ\83Ñ\86и Ñ\9bе Ñ\81е налазиÑ\82и веза Ñ\81 потврдним кодом;\nунесите је у прегледач да бисте потврдили да је ваша имејл адреса важећа.",
- "confirmemail_pending": "Ð\9fоÑ\82вÑ\80дни код вам Ñ\98е веÑ\9b поÑ\81лаÑ\82. Ð\90ко Ñ\81Ñ\82е недавно оÑ\82воÑ\80или налог, онда веÑ\80оваÑ\82но Ñ\82Ñ\80еба да Ñ\81аÑ\87екаÑ\82е неколико минÑ\83Ñ\82а да пÑ\80иÑ\81Ñ\82игне, пре него што поново затражите нови код.",
- "confirmemail_send": "Ð\9fоÑ\88аÑ\99и поÑ\82вÑ\80дни код",
+ "confirmemail": "Потврда имејл-адресе",
+ "confirmemail_noemail": "Нисте поставили важећу имејл-адресу у [[Special:Preferences|корисничким подешавањима]].",
+ "confirmemail_text": "{{SITENAME}} заÑ\85Ñ\82ева да поÑ\82вÑ\80диÑ\82е имеÑ\98л адÑ\80еÑ\81Ñ\83 пÑ\80е него Ñ\88Ñ\82о поÑ\87неÑ\82е да коÑ\80иÑ\81Ñ\82иÑ\82е могÑ\83Ñ\9bноÑ\81Ñ\82и имеÑ\98ла.\nÐ\9aликниÑ\82е на дÑ\83гме иÑ\81под за Ñ\81лаÑ\9aе поÑ\80Ñ\83ке на ваÑ\88Ñ\83 адÑ\80еÑ\81Ñ\83.\nУ поÑ\80Ñ\83Ñ\86и Ñ\9bе Ñ\81е налазиÑ\82и линк Ñ\81а потврдним кодом;\nунесите је у прегледач да бисте потврдили да је ваша имејл адреса важећа.",
+ "confirmemail_pending": "Ð\9aод за поÑ\82вÑ\80дÑ\83 вам Ñ\98е веÑ\9b поÑ\81лаÑ\82 имеÑ\98лом.\nÐ\90ко Ñ\81Ñ\82е недавно оÑ\82воÑ\80или налог, можда Ñ\82Ñ\80еба да Ñ\81аÑ\87екаÑ\82е неколико минÑ\83Ñ\82а да пÑ\80иÑ\81Ñ\82игне пре него што поново затражите нови код.",
+ "confirmemail_send": "Ð\9fоÑ\88аÑ\99и код за поÑ\82вÑ\80дÑ\83",
"confirmemail_sent": "Потврдна порука је послата.",
- "confirmemail_oncreate": "Ð\9fоÑ\81лаÑ\82 Ñ\98е поÑ\82вÑ\80дни код на вашу имејл адресу.\nОвај код није потребан за пријављивање, али вам треба да бисте укључили могућности имејла на викију.",
+ "confirmemail_oncreate": "Ð\9fоÑ\81лаÑ\82 Ñ\98е код за поÑ\82вÑ\80дÑ\83 на вашу имејл адресу.\nОвај код није потребан за пријављивање, али вам треба да бисте укључили могућности имејла на викију.",
"confirmemail_sendfailed": "{{SITENAME}} не може да пошаље имејл потврду.\nПроверите да ли је имејл адреса правилно написана.\n\nГрешка: $1",
- "confirmemail_invalid": "Ð\9fоÑ\82вÑ\80дни код Ñ\98е неиÑ\81пÑ\80аван. Ð\92еÑ\80оваÑ\82но Ñ\98е истекао.",
- "confirmemail_needlogin": "Морате бити $1 да бисте потврдили имејл адресу.",
- "confirmemail_success": "Ваша имејл адреса је потврђена.\nСада можете да се [[Special:UserLogin|пријавите]] и уживате у викију.",
- "confirmemail_loggedin": "Ваша имејл адреса је сада потврђена.",
- "confirmemail_subject": "{{SITENAME}} – потврда имејл адресе",
- "confirmemail_body": "Неко, вероватно Ви, с IP адресе $1,\nотворио је налог „$2“ с овом имејл адресом на пројекту {{SITENAME}}.\n\nДа потврдите да овај налог стварно припада вама и да активирате\nмогућности имејла на пројекту {{SITENAME}}, отворите ову везу у прегледачу:\n\n$3\n\nУколико налог *не* припада вама, пратите везу\nда откажете потврду имејл адресе:\n\n$5\n\nОвај потврдни код истиче у $4.",
- "confirmemail_body_changed": "Неко, вероватно Ви, с IP адресе $1,\nпроменио је имејл адресу налога „$2“ у ову адресу на пројекту {{SITENAME}}.\n\nДа бисте потврдили да овај налог стварно припада вама и поново активирали могућности имејла, отворите следећу везу у прегледачу:\n\n$3\n\nАко налог *не* припада вама, пратите следећу везу да откажете потврду имејл адресе:\n\n$5\n\nОвај потврдни код истиче $6 у $7",
- "confirmemail_body_set": "Неко, вероватно Ви, с IP адресе $1,\nпроменио је имејл адресу налога „$2“ у ову адресу на {{SITENAME}}.\n\nДа бисмо потврдили да овај налог стварно припада вама и поново активирали\nмогућности имејла на {{SITENAME}}, отворите следећу везу у прегледачу:\n\n$3\n\nАко налог *не* припада вама, пратите следећу везу да откажете потврду имејл адресе:\n\n$5\n\nОвај потврдни код истиче $4.",
+ "confirmemail_invalid": "Ð\9dеважеÑ\9bи код за поÑ\82вÑ\80дÑ\83.\nÐ\9aод Ñ\98е можда истекао.",
+ "confirmemail_needlogin": "Морате бити $1 да бисте потврдили своју имејл-адресу.",
+ "confirmemail_success": "Ваша имејл-адреса је потврђена.\nСада можете да се [[Special:UserLogin|пријавите]] и уживате у викију.",
+ "confirmemail_loggedin": "Ваша имејл-адреса је сада потврђена.",
+ "confirmemail_subject": "{{SITENAME}} – потврда имејл-адресе",
+ "confirmemail_body": "Неко, вероватно Ви, са IP адресе $1,\nрегистровао је налог „$2“ са овом имејл адресом на пројекту {{SITENAME}}.\n\nДа бисте потврдили да овај налог стварно припада вама и активирали могућности имејла на пројекту {{SITENAME}}, отворите овај линк у прегледачу:\n\n$3\n\nАко ви *нисте* регистровали налог, пратите овај линк\nда бисте отказали потврду имејл адресе:\n\n$5\n\nОвај код за потврду истиче у $4.",
+ "confirmemail_body_changed": "Неко, вероватно Ви, с IP адресе $1,\nпроменио је имејл адресу налога „$2“ у ову адресу на пројекту {{SITENAME}}.\n\nДа бисте потврдили да овај налог стварно припада вама и поново активирали могућности имејла, отворите следећи линк у прегледачу:\n\n$3\n\nАко налог *не* припада вама, пратите следећи линк да откажете потврду имејл адресе:\n\n$5\n\nОвај код за потврду истиче $6 у $7",
+ "confirmemail_body_set": "Неко, вероватно Ви, с IP адресе $1,\nпроменио је имејл адресу налога „$2“ у ову адресу на {{SITENAME}}.\n\nДа бисмо потврдили да овај налог стварно припада вама и поново активирали\nмогућности имејла на {{SITENAME}}, отворите следећи линк у прегледачу:\n\n$3\n\nАко налог *не* припада вама, пратите следећи линк да откажете потврду имејл адресе:\n\n$5\n\nОвај код за потврду истиче $4.",
"confirmemail_invalidated": "Потврда имејл адресе је отказана",
"invalidateemail": "Отказивање потврде имејла",
"notificationemail_subject_changed": "Регистрована имејл адреса на пројекту {{SITENAME}} је промењена",
"scarytranscludefailed": "[Добављање шаблона за $1 није успело]",
"scarytranscludefailed-httpstatus": "[Не могу да преузмем шаблон $1: HTTP $2]",
"scarytranscludetoolong": "[URL адреса је предугачка]",
- "deletedwhileediting": "<strong>УпозоÑ\80еÑ\9aе</strong>: Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е обрисана након што сте почели са уређивањем!",
+ "deletedwhileediting": "<strong>УпозоÑ\80еÑ\9aе</strong>: Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е избрисана након што сте почели са уређивањем!",
"confirmrecreate": "{{GENDER:$1|Корисник|Корисница}} [[User:$1|$1]] ([[User talk:$1|разговор]]) је {{GENDER:$1|обрисао|обрисала}} ову страницу након што сте почели да је уређујете из следећег разлога:\n: <em>$2</em>\nПотврдите да стварно желите да направите страницу.",
"confirmrecreate-noreason": "{{GENDER:$1|Корисник|Корисница}} [[User:$1|$1]] ([[User talk:$1|разговор]]) је {{GENDER:$1|обрисао|обрисала}} ову страницу након што сте почели да је уређујете. Потврдите да стварно желите да поново направите ову страницу.",
"recreate": "Поново направи",
"confirm-unwatch-top": "Уклонити ову страницу са списка надгледања?",
"confirm-rollback-button": "У реду",
"confirm-rollback-top": "Врати измене на овој страници?",
+ "confirm-mcrundo-title": "Поништавање промене",
+ "mcrundofailed": "Поништавање није успело",
+ "mcrundo-missingparam": "Недостаје потребан параметар на захтеву.",
+ "mcrundo-changed": "Страница је промењена док сте гледали разлику. Прегледајте нову промену.",
"semicolon-separator": "; ",
"comma-separator": ", ",
"colon-separator": ": ",
"imgmultigo": "Иди!",
"imgmultigoto": "Иди на страницу $1",
"img-lang-default": "(подразумевани језик)",
- "img-lang-info": "Ð\9fÑ\80икажи овÑ\83 Ñ\81ликÑ\83 на $1. $2",
+ "img-lang-info": "РендеÑ\80Ñ\83Ñ\98 овÑ\83 Ñ\81ликÑ\83 Ñ\83 $1. $2",
"img-lang-go": "Иди",
"ascending_abbrev": "раст.",
"descending_abbrev": "опад.",
"watchlistedit-too-many": "Има превише страница за приказ овде.",
"watchlisttools-clear": "очисти списак надгледања",
"watchlisttools-view": "погледај релевантне промене",
- "watchlisttools-edit": "прикажи и уреди списак надгледања",
+ "watchlisttools-edit": "погледај и уреди списак надгледања",
"watchlisttools-raw": "уреди сиров списак надгледања",
"iranian-calendar-m1": "Фарвардин",
"iranian-calendar-m2": "Ордибехешт",
"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": "Тип вредности:",
"specialpages-group-developer": "Програмерске алатке",
"blankpage": "Празна страница",
"intentionallyblankpage": "Ова страница је намерно остављена празном.",
- "external_image_whitelist": " #Ð\9eÑ\81Ñ\82авиÑ\82е оваÑ\98 Ñ\80ед онаквим какав Ñ\98еÑ\81Ñ\82е<pre>\n#Ð\98Ñ\81под додаÑ\98Ñ\82е одломке Ñ\80егÑ\83лаÑ\80ниÑ\85 изÑ\80аза (Ñ\81амо део коÑ\98и Ñ\81е налази измеÑ\92Ñ\83 //)\n#Ð\9eни Ñ\9bе биÑ\82и Ñ\83поÑ\80еÑ\92ени Ñ\81 адÑ\80еÑ\81ама Ñ\81поÑ\99аÑ\88Ñ\9aиÑ\85 Ñ\81лика\n#Ð\9eне коÑ\98е Ñ\81е поклапаÑ\98Ñ\83 биÑ\9bе пÑ\80иказане као Ñ\81лике, а пÑ\80еоÑ\81Ñ\82але као везе до слика\n#Редови који почињу с тарабом се сматрају коментарима\n#Сви уноси су осетљиви на мала и велика слова\n\n#Додајте све одломке регуларних израза изнад овог реда. Овај ред не дирајте</pre>",
+ "external_image_whitelist": " #Ð\9eÑ\81Ñ\82авиÑ\82е оваÑ\98 Ñ\80ед онаквим какав Ñ\98еÑ\81Ñ\82е<pre>\n#Ð\98Ñ\81под додаÑ\98Ñ\82е одломке Ñ\80егÑ\83лаÑ\80ниÑ\85 изÑ\80аза (Ñ\81амо део коÑ\98и Ñ\81е налази измеÑ\92Ñ\83 //)\n#Ð\9eни Ñ\9bе биÑ\82и Ñ\83поÑ\80еÑ\92ени Ñ\81 адÑ\80еÑ\81ама Ñ\81поÑ\99аÑ\88Ñ\9aиÑ\85 Ñ\81лика\n#Ð\9eне коÑ\98е Ñ\81е поклапаÑ\98Ñ\83 биÑ\9bе пÑ\80иказане као Ñ\81лике, а пÑ\80еоÑ\81Ñ\82але као линкови до слика\n#Редови који почињу с тарабом се сматрају коментарима\n#Сви уноси су осетљиви на мала и велика слова\n\n#Додајте све одломке регуларних израза изнад овог реда. Овај ред не дирајте</pre>",
"tags": "Важеће ознаке промена",
"tag-filter": "Филтер [[Special:Tags|ознака]]:",
"tag-filter-submit": "Филтрирај",
"tags-source-manual": "Ручно је додају корисници и ботови",
"tags-source-none": "Ван употребе",
"tags-edit": "уреди",
- "tags-delete": "обриши",
+ "tags-delete": "избриши",
"tags-activate": "активирај",
"tags-deactivate": "деактивирај",
"tags-hitcount": "$1 {{PLURAL:$1|промена|промене|промена}}",
"tags-create-warnings-below": "Правите нову ознаку, желите ли да наставите?",
"tags-delete-title": "Брисање ознака",
"tags-delete-explanation-initial": "Бришете ознаку „$1“ из базе података.",
- "tags-delete-explanation-warning": "Ова радња је <strong>неповратна</strong> и <strong>не може се поништити</strong>, чак ни администратори базе података је не могу поништити. Будите сигурни да је ово ознака коју желите обрисати.",
+ "tags-delete-explanation-warning": "Ова радња је <strong>неповратна</strong> и <strong>не може да се поништи</strong>. Ово не могу да ураде чак ни администратори базе података. Будите сигурни да је ово ознака коју желите избрисати.",
"tags-delete-reason": "Разлог:",
- "tags-delete-submit": "Ð\9dеповÑ\80аÑ\82но обриши ову ознаку",
+ "tags-delete-submit": "Ð\9dеповÑ\80аÑ\82но избриши ову ознаку",
"tags-delete-not-found": "Ознака „$1“ не постоји.",
- "tags-delete-too-many-uses": "Ð\9eзнака â\80\9e$1â\80\9d Ñ\98е пÑ\80имеÑ\9aена на виÑ\88е од $2 {{PLURAL:$2|Ñ\80евизиÑ\98е|Ñ\80евизиÑ\98а}}, Ñ\88Ñ\82о знаÑ\87и да Ñ\81е не може обрисати.",
+ "tags-delete-too-many-uses": "Ð\9eзнака â\80\9e$1â\80\9d Ñ\98е пÑ\80имеÑ\9aена на виÑ\88е од $2 {{PLURAL:$2|Ñ\80евизиÑ\98е|Ñ\80евизиÑ\98а}}, Ñ\88Ñ\82о знаÑ\87и да Ñ\81е не може избрисати.",
"tags-delete-no-permission": "Немате дозволу да бришете ознаке промена.",
"tags-activate-title": "Активирање ознака",
"tags-activate-question": "Активирате ознаку „$1“.",
"compare-rev1": "Ревизија 1",
"compare-rev2": "Измена 2",
"compare-submit": "Упореди",
- "compare-invalid-title": "Наведени наслов је неисправан.",
+ "compare-invalid-title": "Наслов који сте навели је неважећи.",
"compare-title-not-exists": "Наведени наслов не постоји.",
"compare-revision-not-exists": "Ревизија коју сте навели не постоји.",
"diff-form": "Разлике",
"diff-form-oldid": "ID старе ревизије (опционално)",
"diff-form-revid": "ID измене или разлике",
"diff-form-submit": "Прикажи разлике",
- "permanentlink": "ТÑ\80аÑ\98на веза",
+ "permanentlink": "ТÑ\80аÑ\98ни линк",
"permanentlink-revid": "ID ревизије",
"permanentlink-submit": "Иди на измену",
"dberr-problems": "Дошло је до техничких проблема.",
"logentry-newusers-create": "$1 је {{GENDER:$2|отворио|отворила}} кориснички налог",
"logentry-newusers-create2": "$1 је {{GENDER:$2|отворио|отворила}} кориснички налог $3",
"logentry-newusers-byemail": "$1 је {{GENDER:$2|отворио|отворила}} кориснички налог $3 и лозинка је послата на имејл",
- "logentry-newusers-autocreate": "Кориснички налог $1 је аутоматски {{GENDER:$2|отворен}}",
+ "logentry-newusers-autocreate": "$1 је аутоматски {{GENDER:$2|отворио|отворила}} кориснички налог",
"logentry-protect-move_prot": "$1 је {{GENDER:$2|преместио|преместила}} подешавања заштите са $4 на $3",
"logentry-protect-unprotect": "$1 je {{GENDER:$2|скинуо|скинула}} заштиту са странице $3",
"logentry-protect-protect": "$1 је {{GENDER:$2|заштитио|заштитила}} $3 $4",
"logentry-rights-rights-legacy": "$1 је {{GENDER:$2|променио|променила}} чланство групе за $3",
"logentry-rights-autopromote": "$1 је аутоматски {{GENDER:$2|унапређен|унапређена}} из $4 у $5",
"logentry-upload-upload": "$1 је {{GENDER:$2|отпремио|отпремила}} $3",
- "logentry-upload-overwrite": "$1 је {{GENDER:$2|отпремио|отпремила}} нову верзију $3",
+ "logentry-upload-overwrite": "$1 је {{GENDER:$2|отпремио|отпремила}} нову верзију датотеке $3",
"logentry-upload-revert": "$1 је {{GENDER:$2|отпремио|отпремила}} $3",
"log-name-managetags": "Евиденција управљања ознакама",
- "log-description-managetags": "Ð\9dа овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\81е налази Ñ\81пиÑ\81ак измена Ñ\83 вези [[Special:Tags|ознака]]. Ð\95виденÑ\86иÑ\98а Ñ\81адÑ\80жи Ñ\81амо Ñ\80адÑ\9aе коÑ\98е Ñ\81Ñ\83 Ñ\80Ñ\83Ñ\87но извÑ\80Ñ\88или админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80и; Ñ\83ноÑ\81и за ознаке коÑ\98е Ñ\98е напÑ\80авио или обрисао вики софтвера се не налазе у овој евиденцији.",
+ "log-description-managetags": "Ð\9dа овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\81е налази Ñ\81пиÑ\81ак измена Ñ\83 вези [[Special:Tags|ознака]]. Ð\95виденÑ\86иÑ\98а Ñ\81адÑ\80жи Ñ\81амо Ñ\80адÑ\9aе коÑ\98е Ñ\81Ñ\83 Ñ\80Ñ\83Ñ\87но извÑ\80Ñ\88или админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80и; Ñ\83ноÑ\81и за ознаке коÑ\98е Ñ\98е напÑ\80авио или избрисао вики софтвера се не налазе у овој евиденцији.",
"logentry-managetags-create": "$1 је {{GENDER:$2|направио|направила}} ознаку „$4“",
"logentry-managetags-delete": "$1 је {{GENDER:$2|обрисао|обрисала}} ознаку „$4“ (уклоњена је из $5 {{PLURAL:$5|ревизије или уноса у евиденцији|ревизија и/или уноса у евиденцији}})",
"logentry-managetags-activate": "$1 је {{GENDER:$2|активирао|активирала}} ознаку „$4“ за употребу од стране корисника и ботова",
"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]“.",
"feedback-thanks-title": "Хвала вам!",
"feedback-useragent": "Кориснички агент:",
"searchsuggest-search": "Претрага",
- "searchsuggest-containing": "садржи...",
- "api-error-badtoken": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aа гÑ\80еÑ\88ка: неиÑ\81пÑ\80аван токен.",
- "api-error-emptypage": "СÑ\82ваÑ\80ање нових празних страница није дозвољено.",
+ "searchsuggest-containing": "садржи…",
+ "api-error-badtoken": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aа гÑ\80еÑ\88ка: лоÑ\88 токен.",
+ "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-summary": "СÑ\82аÑ\82иÑ\81Ñ\82ике о Ñ\82иповима поÑ\81лаÑ\82иÑ\85 даÑ\82оÑ\82ека. Ð\9eвде Ñ\81Ñ\83 Ñ\83Ñ\80аÑ\87Ñ\83наÑ\82е Ñ\81амо наÑ\98Ñ\81коÑ\80иÑ\98е веÑ\80зиÑ\98е даÑ\82оÑ\82ека. СÑ\82аÑ\80е или обрисане верзије нису урачунате.",
+ "mediastatistics-summary": "СÑ\82аÑ\82иÑ\81Ñ\82ике о Ñ\82иповима оÑ\82пÑ\80емÑ\99ениÑ\85 даÑ\82оÑ\82ека. Ð\9eвде Ñ\81Ñ\83 Ñ\83Ñ\80аÑ\87Ñ\83наÑ\82е Ñ\81амо наÑ\98Ñ\81коÑ\80иÑ\98е веÑ\80зиÑ\98е даÑ\82оÑ\82ека. СÑ\82аÑ\80е или избрисане верзије нису урачунате.",
"mediastatistics-nbytes": "{{PLURAL:$1|$1 бајт|$1 бајта|$1 бајтова}} ($2; $3%)",
"mediastatistics-bytespertype": "Укупна величина датотеке овог одељка: {{PLURAL:$1|$1 бајт|$1 бајта|$1 бајтова}} ($2; $3%).",
"mediastatistics-allbytes": "Укупна величина свих датотека: {{PLURAL:$1|$1 бајт|$1 бајта|$1 бајтова}} ($2).",
"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": "Аудио",
- "mediastatistics-header-video": "Видео",
+ "mediastatistics-header-audio": "Звук",
+ "mediastatistics-header-video": "Видеи",
+ "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",
"json-error-recursion": "Једна или више рекурзивних референци у вредности коју треба енкодирати.",
"json-error-inf-or-nan": "Једна или више NAN или INF вредности у вредности коју треба енкодирати",
"json-error-unsupported-type": "Дата је вредност типа која се не може енкодирати",
- "headline-anchor-title": "Ð\92еза до овог одељка",
+ "headline-anchor-title": "Ð\9bинк до овог одељка",
"special-characters-group-latin": "Латиница",
"special-characters-group-latinextended": "Проширена латиница",
"special-characters-group-ipa": "МФА",
"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-categoryselector-add-category-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": "Ð\9dеиÑ\81пÑ\80аван домен.",
- "authmanager-autocreate-noperm": "Ð\90Ñ\83Ñ\82омаÑ\82Ñ\81ко пÑ\80авÑ\99ење налога није дозвољено.",
+ "authmanager-authplugin-setpass-bad-domain": "Ð\9dеважеÑ\9bи домен.",
+ "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": "Потврда идентитета лозинком",
"authform-wrongtoken": "Погрешан токен",
"specialpage-securitylevel-not-allowed-title": "Није дозвољено",
"specialpage-securitylevel-not-allowed": "Жао нам је, није вам дозвољено да користите ову страницу јер не могу да потврдим ваш идентитет.",
- "authpage-cannot-login": "Ð\9dе могÑ\83 запоÑ\87еÑ\82и пријаву.",
+ "authpage-cannot-login": "Ð\9dе могÑ\83 да запоÑ\87нем пријаву.",
"authpage-cannot-login-continue": "Не могу да наставим са пријавом. Ваша сесија је највероватније истекла.",
- "authpage-cannot-create": "Ð\9dе могÑ\83 запоÑ\87еÑ\82и Ñ\81тварање налога.",
- "authpage-cannot-link": "Ð\9dе могÑ\83 запоÑ\87еÑ\82и Ñ\81паÑ\98ање налога.",
+ "authpage-cannot-create": "Ð\9dе могÑ\83 да запоÑ\87нем отварање налога.",
+ "authpage-cannot-link": "Ð\9dе могÑ\83 да запоÑ\87нем повезивање налога.",
"cannotauth-not-allowed-title": "Приступ је одбијен",
"cannotauth-not-allowed": "Није вам дозвољено да користите ову страницу",
"changecredentials": "Промена акредитива",
"pageid": "ID странице: $1",
"rawhtml-notallowed": "<html> тагови не могу да се користе ван нормалних страница.",
"gotointerwiki": "Напуштање пројекта {{SITENAME}}",
- "gotointerwiki-invalid": "Ð\9eдабÑ\80ани наслов је невалидан.",
+ "gotointerwiki-invalid": "Ð\9dаведени наслов је невалидан.",
"gotointerwiki-external": "Управо ћете да напустите пројекат {{SITENAME}} да бисте на засебном веб-сајту посетили [[$2]].\n\n'''[$1 Продужи на $1]'''",
"undelete-cantedit": "Не можете повратити ову страницу јер немате дозволу да је уређујете.",
"undelete-cantcreate": "Не можете повратити ову страницу јер нема постојеће странице са овим именом и немате дозволу да направите ову страницу.",
"pagedata-title": "Подаци странице",
"pagedata-not-acceptable": "Није пронађен одговарајући облик. Подржане MIME-врсте: $1",
- "pagedata-bad-title": "Ð\9dеважеÑ\9bи наслов: $1.",
+ "pagedata-bad-title": "Ð\9dевалидан наслов: $1.",
"passwordpolicies": "Правила за лозинке",
"passwordpolicies-group": "Група",
"passwordpolicies-policies": "Правила",
"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": "搜尋媒體名稱:",
"magiclink-tracking-isbn": "使用 ISBN 魔法連結的頁面",
"magiclink-tracking-isbn-desc": "此頁面使用 ISBN 魔法連結的頁面,請參考 [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org] 的如何遷移。",
"specialloguserlabel": "執行者:",
- "speciallogtitlelabel": "目標 (標題或以 {{ns:user}}:使用者 表示使用者):",
+ "speciallogtitlelabel": "目標(標題或以 {{ns:user}}:使用者名稱 表示使用者):",
"log": "日誌",
"logeventslist-submit": "顯示",
"logeventslist-more-filters": "顯示額外日誌:",
"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}}
--- /dev/null
+<?php
+
+/**
+ * Adds a change tag to the wiki.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Maintenance
+ */
+
+require_once __DIR__ . '/Maintenance.php';
+
+/**
+ * Adds a change tag to the wiki
+ *
+ * @ingroup Maintenance
+ * @since 1.32
+ */
+class AddChangeTag extends Maintenance {
+
+ public function __construct() {
+ parent::__construct();
+ $this->addDescription( 'Adds a change tag to the wiki.' );
+
+ $this->addOption( 'tag', 'Tag to add', true, true );
+ $this->addOption( 'reason', 'Reason for adding the tag', true, true );
+ }
+
+ public function execute() {
+ $user = User::newSystemUser( 'Maintenance script', [ 'steal' => true ] );
+
+ $tag = $this->getOption( 'tag' );
+
+ $status = ChangeTags::createTagWithChecks(
+ $tag,
+ $this->getOption( 'reason' ),
+ $user
+ );
+
+ if ( !$status->isGood() ) {
+ $this->fatalError( $status->getWikiText( null, null, 'en' ) );
+ }
+
+ $this->output( "$tag was created.\n" );
+ }
+}
+
+$maintClass = AddChangeTag::class;
+require_once RUN_MAINTENANCE_IF_MAIN;
+++ /dev/null
-<?php
-/**
- * Base classes for database dumpers
- *
- * Copyright © 2005 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Dump Maintenance
- */
-
-require_once __DIR__ . '/Maintenance.php';
-
-use MediaWiki\MediaWikiServices;
-use Wikimedia\Rdbms\LoadBalancer;
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * @ingroup Dump Maintenance
- */
-class BackupDumper extends Maintenance {
- public $reporting = true;
- public $pages = null; // all pages
- public $skipHeader = false; // don't output <mediawiki> and <siteinfo>
- public $skipFooter = false; // don't output </mediawiki>
- public $startId = 0;
- public $endId = 0;
- public $revStartId = 0;
- public $revEndId = 0;
- public $dumpUploads = false;
- public $dumpUploadFileContents = false;
- public $orderRevs = false;
-
- protected $reportingInterval = 100;
- protected $pageCount = 0;
- protected $revCount = 0;
- protected $server = null; // use default
- protected $sink = null; // Output filters
- protected $lastTime = 0;
- protected $pageCountLast = 0;
- protected $revCountLast = 0;
-
- protected $outputTypes = [];
- protected $filterTypes = [];
-
- protected $ID = 0;
-
- /**
- * The dependency-injected database to use.
- *
- * @var IDatabase|null
- *
- * @see self::setDB
- */
- protected $forcedDb = null;
-
- /** @var LoadBalancer */
- protected $lb;
-
- // @todo Unused?
- private $stubText = false; // include rev_text_id instead of text; for 2-pass dump
-
- /**
- * @param array|null $args For backward compatibility
- */
- function __construct( $args = null ) {
- parent::__construct();
- $this->stderr = fopen( "php://stderr", "wt" );
-
- // Built-in output and filter plugins
- $this->registerOutput( 'file', DumpFileOutput::class );
- $this->registerOutput( 'gzip', DumpGZipOutput::class );
- $this->registerOutput( 'bzip2', DumpBZip2Output::class );
- $this->registerOutput( 'dbzip2', DumpDBZip2Output::class );
- $this->registerOutput( '7zip', Dump7ZipOutput::class );
-
- $this->registerFilter( 'latest', DumpLatestFilter::class );
- $this->registerFilter( 'notalk', DumpNotalkFilter::class );
- $this->registerFilter( 'namespace', DumpNamespaceFilter::class );
-
- // These three can be specified multiple times
- $this->addOption( 'plugin', 'Load a dump plugin class. Specify as <class>[:<file>].',
- false, true, false, true );
- $this->addOption( 'output', 'Begin a filtered output stream; Specify as <type>:<file>. ' .
- '<type>s: file, gzip, bzip2, 7zip, dbzip2', false, true, false, true );
- $this->addOption( 'filter', 'Add a filter on an output branch. Specify as ' .
- '<type>[:<options>]. <types>s: latest, notalk, namespace', false, true, false, true );
- $this->addOption( 'report', 'Report position and speed after every n pages processed. ' .
- 'Default: 100.', false, true );
- $this->addOption( 'server', 'Force reading from MySQL server', false, true );
- $this->addOption( '7ziplevel', '7zip compression level for all 7zip outputs. Used for ' .
- '-mx option to 7za command.', false, true );
-
- if ( $args ) {
- // Args should be loaded and processed so that dump() can be called directly
- // instead of execute()
- $this->loadWithArgv( $args );
- $this->processOptions();
- }
- }
-
- /**
- * @param string $name
- * @param string $class Name of output filter plugin class
- */
- function registerOutput( $name, $class ) {
- $this->outputTypes[$name] = $class;
- }
-
- /**
- * @param string $name
- * @param string $class Name of filter plugin class
- */
- function registerFilter( $name, $class ) {
- $this->filterTypes[$name] = $class;
- }
-
- /**
- * Load a plugin and register it
- *
- * @param string $class Name of plugin class; must have a static 'register'
- * method that takes a BackupDumper as a parameter.
- * @param string $file Full or relative path to the PHP file to load, or empty
- */
- function loadPlugin( $class, $file ) {
- if ( $file != '' ) {
- require_once $file;
- }
- $register = [ $class, 'register' ];
- $register( $this );
- }
-
- function execute() {
- throw new MWException( 'execute() must be overridden in subclasses' );
- }
-
- /**
- * Processes arguments and sets $this->$sink accordingly
- */
- function processOptions() {
- $sink = null;
- $sinks = [];
-
- $options = $this->orderedOptions;
- foreach ( $options as $arg ) {
- $opt = $arg[0];
- $param = $arg[1];
-
- switch ( $opt ) {
- case 'plugin':
- $val = explode( ':', $param );
-
- if ( count( $val ) === 1 ) {
- $this->loadPlugin( $val[0], '' );
- } elseif ( count( $val ) === 2 ) {
- $this->loadPlugin( $val[0], $val[1] );
- } else {
- $this->fatalError( 'Invalid plugin parameter' );
- return;
- }
-
- break;
- case 'output':
- $split = explode( ':', $param, 2 );
- if ( count( $split ) !== 2 ) {
- $this->fatalError( 'Invalid output parameter' );
- }
- list( $type, $file ) = $split;
- if ( !is_null( $sink ) ) {
- $sinks[] = $sink;
- }
- if ( !isset( $this->outputTypes[$type] ) ) {
- $this->fatalError( "Unrecognized output sink type '$type'" );
- }
- $class = $this->outputTypes[$type];
- if ( $type === "7zip" ) {
- $sink = new $class( $file, intval( $this->getOption( '7ziplevel' ) ) );
- } else {
- $sink = new $class( $file );
- }
-
- break;
- case 'filter':
- if ( is_null( $sink ) ) {
- $sink = new DumpOutput();
- }
-
- $split = explode( ':', $param );
- $key = $split[0];
-
- if ( !isset( $this->filterTypes[$key] ) ) {
- $this->fatalError( "Unrecognized filter type '$key'" );
- }
-
- $type = $this->filterTypes[$key];
-
- if ( count( $split ) === 1 ) {
- $filter = new $type( $sink );
- } elseif ( count( $split ) === 2 ) {
- $filter = new $type( $sink, $split[1] );
- } else {
- $this->fatalError( 'Invalid filter parameter' );
- }
-
- // references are lame in php...
- unset( $sink );
- $sink = $filter;
-
- break;
- }
- }
-
- if ( $this->hasOption( 'report' ) ) {
- $this->reportingInterval = intval( $this->getOption( 'report' ) );
- }
-
- if ( $this->hasOption( 'server' ) ) {
- $this->server = $this->getOption( 'server' );
- }
-
- if ( is_null( $sink ) ) {
- $sink = new DumpOutput();
- }
- $sinks[] = $sink;
-
- if ( count( $sinks ) > 1 ) {
- $this->sink = new DumpMultiWriter( $sinks );
- } else {
- $this->sink = $sink;
- }
- }
-
- function dump( $history, $text = WikiExporter::TEXT ) {
- # Notice messages will foul up your XML output even if they're
- # relatively harmless.
- if ( ini_get( 'display_errors' ) ) {
- ini_set( 'display_errors', 'stderr' );
- }
-
- $this->initProgress( $history );
-
- $db = $this->backupDb();
- $exporter = new WikiExporter( $db, $history, WikiExporter::STREAM, $text );
- $exporter->dumpUploads = $this->dumpUploads;
- $exporter->dumpUploadFileContents = $this->dumpUploadFileContents;
-
- $wrapper = new ExportProgressFilter( $this->sink, $this );
- $exporter->setOutputSink( $wrapper );
-
- if ( !$this->skipHeader ) {
- $exporter->openStream();
- }
- # Log item dumps: all or by range
- if ( $history & WikiExporter::LOGS ) {
- if ( $this->startId || $this->endId ) {
- $exporter->logsByRange( $this->startId, $this->endId );
- } else {
- $exporter->allLogs();
- }
- } elseif ( is_null( $this->pages ) ) {
- # Page dumps: all or by page ID range
- if ( $this->startId || $this->endId ) {
- $exporter->pagesByRange( $this->startId, $this->endId, $this->orderRevs );
- } elseif ( $this->revStartId || $this->revEndId ) {
- $exporter->revsByRange( $this->revStartId, $this->revEndId );
- } else {
- $exporter->allPages();
- }
- } else {
- # Dump of specific pages
- $exporter->pagesByName( $this->pages );
- }
-
- if ( !$this->skipFooter ) {
- $exporter->closeStream();
- }
-
- $this->report( true );
- }
-
- /**
- * Initialise starting time and maximum revision count.
- * We'll make ETA calculations based an progress, assuming relatively
- * constant per-revision rate.
- * @param int $history WikiExporter::CURRENT or WikiExporter::FULL
- */
- function initProgress( $history = WikiExporter::FULL ) {
- $table = ( $history == WikiExporter::CURRENT ) ? 'page' : 'revision';
- $field = ( $history == WikiExporter::CURRENT ) ? 'page_id' : 'rev_id';
-
- $dbr = $this->forcedDb;
- if ( $this->forcedDb === null ) {
- $dbr = wfGetDB( DB_REPLICA );
- }
- $this->maxCount = $dbr->selectField( $table, "MAX($field)", '', __METHOD__ );
- $this->startTime = microtime( true );
- $this->lastTime = $this->startTime;
- $this->ID = getmypid();
- }
-
- /**
- * @todo Fixme: the --server parameter is currently not respected, as it
- * doesn't seem terribly easy to ask the load balancer for a particular
- * connection by name.
- * @return IDatabase
- */
- function backupDb() {
- if ( $this->forcedDb !== null ) {
- return $this->forcedDb;
- }
-
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
- $this->lb = $lbFactory->newMainLB();
- $db = $this->lb->getConnection( DB_REPLICA, 'dump' );
-
- // Discourage the server from disconnecting us if it takes a long time
- // to read out the big ol' batch query.
- $db->setSessionOptions( [ 'connTimeout' => 3600 * 24 ] );
-
- return $db;
- }
-
- /**
- * Force the dump to use the provided database connection for database
- * operations, wherever possible.
- *
- * @param IDatabase|null $db (Optional) the database connection to use. If null, resort to
- * use the globally provided ways to get database connections.
- */
- function setDB( IDatabase $db = null ) {
- parent::setDB( $db );
- $this->forcedDb = $db;
- }
-
- function __destruct() {
- if ( isset( $this->lb ) ) {
- $this->lb->closeAll();
- }
- }
-
- function backupServer() {
- global $wgDBserver;
-
- return $this->server
- ? $this->server
- : $wgDBserver;
- }
-
- function reportPage() {
- $this->pageCount++;
- }
-
- function revCount() {
- $this->revCount++;
- $this->report();
- }
-
- function report( $final = false ) {
- if ( $final xor ( $this->revCount % $this->reportingInterval == 0 ) ) {
- $this->showReport();
- }
- }
-
- function showReport() {
- if ( $this->reporting ) {
- $now = wfTimestamp( TS_DB );
- $nowts = microtime( true );
- $deltaAll = $nowts - $this->startTime;
- $deltaPart = $nowts - $this->lastTime;
- $this->pageCountPart = $this->pageCount - $this->pageCountLast;
- $this->revCountPart = $this->revCount - $this->revCountLast;
-
- if ( $deltaAll ) {
- $portion = $this->revCount / $this->maxCount;
- $eta = $this->startTime + $deltaAll / $portion;
- $etats = wfTimestamp( TS_DB, intval( $eta ) );
- $pageRate = $this->pageCount / $deltaAll;
- $revRate = $this->revCount / $deltaAll;
- } else {
- $pageRate = '-';
- $revRate = '-';
- $etats = '-';
- }
- if ( $deltaPart ) {
- $pageRatePart = $this->pageCountPart / $deltaPart;
- $revRatePart = $this->revCountPart / $deltaPart;
- } else {
- $pageRatePart = '-';
- $revRatePart = '-';
- }
- $this->progress( sprintf(
- "%s: %s (ID %d) %d pages (%0.1f|%0.1f/sec all|curr), "
- . "%d revs (%0.1f|%0.1f/sec all|curr), ETA %s [max %d]",
- $now, wfWikiID(), $this->ID, $this->pageCount, $pageRate,
- $pageRatePart, $this->revCount, $revRate, $revRatePart, $etats,
- $this->maxCount
- ) );
- $this->lastTime = $nowts;
- $this->revCountLast = $this->revCount;
- }
- }
-
- function progress( $string ) {
- if ( $this->reporting ) {
- fwrite( $this->stderr, $string . "\n" );
- }
- }
-}
// Run benchmarks
$stat = new RunningStat();
for ( $i = 0; $i < $count; $i++ ) {
+ // Setup outside of time measure for each loop
+ if ( isset( $bench['setupEach'] ) ) {
+ $bench['setupEach']();
+ }
$t = microtime( true );
call_user_func_array( $bench['function'], $bench['args'] );
$t = ( microtime( true ) - $t ) * 1000;
+++ /dev/null
-<?php
-/**
- * Benchmark for strtr() vs str_replace().
- *
- * This come from r75429 message.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Benchmark
- */
-
-require_once __DIR__ . '/Benchmarker.php';
-
-function bfNormalizeTitleStrTr( $str ) {
- return strtr( $str, '_', ' ' );
-}
-
-function bfNormalizeTitleStrReplace( $str ) {
- return str_replace( '_', ' ', $str );
-}
-
-/**
- * Maintenance script that benchmarks for strtr() vs str_replace().
- *
- * @ingroup Benchmark
- */
-class BenchStrtrStrReplace extends Benchmarker {
- public function __construct() {
- parent::__construct();
- $this->addDescription( 'Benchmark for strtr() vs str_replace().' );
- }
-
- public function execute() {
- $this->bench( [
- [ 'function' => [ $this, 'benchstrtr' ] ],
- [ 'function' => [ $this, 'benchstr_replace' ] ],
- [ 'function' => [ $this, 'benchstrtr_indirect' ] ],
- [ 'function' => [ $this, 'benchstr_replace_indirect' ] ],
- ] );
- }
-
- protected function benchstrtr() {
- strtr( "[[MediaWiki:Some_random_test_page]]", "_", " " );
- }
-
- protected function benchstr_replace() {
- str_replace( "_", " ", "[[MediaWiki:Some_random_test_page]]" );
- }
-
- protected function benchstrtr_indirect() {
- bfNormalizeTitleStrTr( "[[MediaWiki:Some_random_test_page]]" );
- }
-
- protected function benchstr_replace_indirect() {
- bfNormalizeTitleStrReplace( "[[MediaWiki:Some_random_test_page]]" );
- }
-}
-
-$maintClass = BenchStrtrStrReplace::class;
-require_once RUN_MAINTENANCE_IF_MAIN;
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Benchmark
+ */
+
+require_once __DIR__ . '/Benchmarker.php';
+
+/**
+ * Maintenance script that benchmarks string replacement methods.
+ *
+ * @ingroup Benchmark
+ */
+class BenchmarkStringReplacement extends Benchmarker {
+ protected $defaultCount = 10000;
+ private $input = 'MediaWiki:Some_random_test_page';
+
+ public function __construct() {
+ parent::__construct();
+ $this->addDescription( 'Benchmark for string replacement methods.' );
+ }
+
+ public function execute() {
+ $this->bench( [
+ 'strtr' => [ $this, 'bench_strtr' ],
+ 'str_replace' => [ $this, 'bench_str_replace' ],
+ ] );
+ }
+
+ protected function bench_strtr() {
+ strtr( $this->input, "_", " " );
+ }
+
+ protected function bench_str_replace() {
+ str_replace( "_", " ", $this->input );
+ }
+}
+
+$maintClass = BenchmarkStringReplacement::class;
+require_once RUN_MAINTENANCE_IF_MAIN;
[
'function' => [ $this, 'getPrefixedTextTitle' ],
],
- [
+ 'parseTitleValue cached' => [
'function' => [ $this, 'parseTitleValue' ],
'setup' => [ $this, 'randomize' ],
],
- [
+ 'parseTitle cached' => [
'function' => [ $this, 'parseTitle' ],
'setup' => [ $this, 'randomize' ],
],
+ 'parseTitleValue no cache' => [
+ 'function' => [ $this, 'parseTitleValue' ],
+ 'setupEach' => [ $this, 'randomize' ],
+ ],
+ 'parseTitle no cache' => [
+ 'function' => [ $this, 'parseTitle' ],
+ 'setupEach' => [ $this, 'randomize' ],
+ ],
] );
}
$this->handleMoves( $dbr, $output );
// We need to handle restores too since delete may have happened in previous update.
$this->handleRestores( $dbr, $output );
+ // Process newly added pages
$this->handleAdds( $dbr, $output );
- $this->handleChanges( $dbr, $output );
+ // Process page edits
+ $this->handleEdits( $dbr, $output );
+ // Process categorization changes
+ $this->handleCategorization( $dbr, $output );
// Update timestamp
fwrite( $output, $this->updateTS( $this->endTS ) );
}
/**
- * Write data for a set of categories
+ * Write parent data for a set of categories.
+ * The list has the child categories.
* @param IDatabase $dbr
- * @param string[] $pages List of categories: id => title
+ * @param string[] $pages List of child categories: id => title
*/
private function writeParentCategories( IDatabase $dbr, $pages ) {
foreach ( $this->getCategoryLinksIterator( $dbr, array_keys( $pages ) ) as $row ) {
}
/**
- * Fetch categorization changes
+ * Fetch categorization changes or edits
* @param IDatabase $dbr
* @return BatchRowIterator
*/
- protected function getChangedCatsIterator( IDatabase $dbr ) {
- $it = $this->setupChangesIterator( $dbr );
+ protected function getChangedCatsIterator( IDatabase $dbr, $type ) {
+ $it =
+ $this->setupChangesIterator( $dbr );
$it->addConditions( [
'rc_namespace' => NS_CATEGORY,
'rc_new' => 0,
- 'rc_type' => [ RC_EDIT, RC_CATEGORIZE ],
+ 'rc_type' => $type,
] );
$this->addIndex( $it );
return $it;
}
/**
+ * Handle edits for category texts
* @param IDatabase $dbr
* @param resource $output
*/
- public function handleChanges( IDatabase $dbr, $output ) {
- foreach ( $this->getChangedCatsIterator( $dbr ) as $batch ) {
+ public function handleEdits( IDatabase $dbr, $output ) {
+ // Editing category can change hidden flag and add new parents.
+ // TODO: it's pretty expensive to update all edited categories, and most edits
+ // aren't actually interesting for us. Some way to know which are interesting?
+ // We can capture recategorization on the next step, but not change in hidden status.
+ foreach ( $this->getChangedCatsIterator( $dbr, RC_EDIT ) as $batch ) {
$pages = [];
$deleteUrls = [];
foreach ( $batch as $row ) {
+ // Note that on categorization event, cur_id points to
+ // the child page, not the parent category!
if ( isset( $this->processed[$row->rc_cur_id] ) ) {
// We already captured this one before
continue;
$deleteUrls[] = '<' . $this->categoriesRdf->labelToUrl( $row->rc_title ) . '>';
}
+ fwrite( $output, $this->getCategoriesUpdate( $dbr, $deleteUrls, $pages, "Edits" ) );
+ }
+ }
+
+ /**
+ * Handles categorization changes
+ * @param IDatabase $dbr
+ * @param resource $output
+ */
+ public function handleCategorization( IDatabase $dbr, $output ) {
+ $processedTitle = [];
+ // Categorization change can add new parents and change counts
+ // for the parent category.
+ foreach ( $this->getChangedCatsIterator( $dbr, RC_CATEGORIZE ) as $batch ) {
+ /*
+ * Note that on categorization event, cur_id points to
+ * the child page, not the parent category!
+ * So we need to have a two-stage process, since we have ID from one
+ * category and title from another, and we need both for proper updates.
+ * TODO: For now, we do full update even though some data hasn't changed,
+ * e.g. parents for parent cat and counts for child cat.
+ */
+ foreach ( $batch as $row ) {
+ $childPages[$row->rc_cur_id] = true;
+ $parentCats[$row->rc_title] = true;
+ }
+
+ $joinConditions = [
+ 'page_props' => [
+ 'LEFT JOIN',
+ [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ],
+ ],
+ 'category' => [
+ 'LEFT JOIN',
+ [ 'cat_title = page_title' ],
+ ],
+ ];
+
+ $pages = [];
+ $deleteUrls = [];
+
+ if ( !empty( $childPages ) ) {
+ // Load child rows by ID
+ $childRows = $dbr->select(
+ [ 'page', 'page_props', 'category' ],
+ [
+ 'page_id',
+ 'rc_title' => 'page_title',
+ 'pp_propname',
+ 'cat_pages',
+ 'cat_subcats',
+ 'cat_files',
+ ],
+ [ 'page_namespace' => NS_CATEGORY, 'page_id' => array_keys( $childPages ) ],
+ __METHOD__,
+ [],
+ $joinConditions
+ );
+ foreach ( $childRows as $row ) {
+ if ( isset( $this->processed[$row->page_id] ) ) {
+ // We already captured this one before
+ continue;
+ }
+ $this->writeCategoryData( $row );
+ $deleteUrls[] = '<' . $this->categoriesRdf->labelToUrl( $row->rc_title ) . '>';
+ $this->processed[$row->page_id] = true;
+ }
+ }
+
+ if ( !empty( $parentCats ) ) {
+ // Load parent rows by title
+ $joinConditions = [
+ 'page' => [
+ 'LEFT JOIN',
+ [ 'page_title = cat_title', 'page_namespace' => NS_CATEGORY ],
+ ],
+ 'page_props' => [
+ 'LEFT JOIN',
+ [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ],
+ ],
+ ];
+
+ $parentRows = $dbr->select(
+ [ 'category', 'page', 'page_props' ],
+ [
+ 'page_id',
+ 'rc_title' => 'cat_title',
+ 'pp_propname',
+ 'cat_pages',
+ 'cat_subcats',
+ 'cat_files',
+ ],
+ [ 'cat_title' => array_keys( $parentCats ) ],
+ __METHOD__,
+ [],
+ $joinConditions
+ );
+ foreach ( $parentRows as $row ) {
+ if ( $row->page_id && isset( $this->processed[$row->page_id] ) ) {
+ // We already captured this one before
+ continue;
+ }
+ if ( isset( $processedTitle[$row->rc_title] ) ) {
+ // We already captured this one before
+ continue;
+ }
+ $this->writeCategoryData( $row );
+ $deleteUrls[] = '<' . $this->categoriesRdf->labelToUrl( $row->rc_title ) . '>';
+ if ( $row->page_id ) {
+ $this->processed[$row->page_id] = true;
+ }
+ $processedTitle[$row->rc_title] = true;
+ }
+ }
+
fwrite( $output, $this->getCategoriesUpdate( $dbr, $deleteUrls, $pages, "Changes" ) );
}
}
$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__ );
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @ingroup Dump Maintenance
+ * @ingroup Dump
+ * @ingroup Maintenance
*/
-require_once __DIR__ . '/backup.inc';
+require_once __DIR__ . '/includes/BackupDumper.php';
class DumpBackup extends BackupDumper {
function __construct( $args = null ) {
* @ingroup Maintenance
*/
-require_once __DIR__ . '/backup.inc';
+require_once __DIR__ . '/includes/BackupDumper.php';
require_once __DIR__ . '/7zip.inc';
require_once __DIR__ . '/../includes/export/WikiExporter.php';
*/
function indexEntry( $filename ) {
return "\t<sitemap>\n" .
- "\t\t<loc>{$this->urlpath}$filename</loc>\n" .
+ "\t\t<loc>" . wfGetServerUrl( PROTO_CANONICAL ) .
+ ( substr( $this->urlpath, 0, 1 ) === "/" ? "" : "/" ) .
+ "{$this->urlpath}$filename</loc>\n" .
"\t\t<lastmod>{$this->timestamp}</lastmod>\n" .
"\t</sitemap>\n";
}
--- /dev/null
+<?php
+/**
+ * Base classes for database-dumping maintenance scripts.
+ *
+ * Copyright © 2005 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Dump
+ * @ingroup Maintenance
+ */
+
+require_once __DIR__ . '/../Maintenance.php';
+
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * @ingroup Dump
+ * @ingroup Maintenance
+ */
+abstract class BackupDumper extends Maintenance {
+ public $reporting = true;
+ public $pages = null; // all pages
+ public $skipHeader = false; // don't output <mediawiki> and <siteinfo>
+ public $skipFooter = false; // don't output </mediawiki>
+ public $startId = 0;
+ public $endId = 0;
+ public $revStartId = 0;
+ public $revEndId = 0;
+ public $dumpUploads = false;
+ public $dumpUploadFileContents = false;
+ public $orderRevs = false;
+
+ protected $reportingInterval = 100;
+ protected $pageCount = 0;
+ protected $revCount = 0;
+ protected $server = null; // use default
+ protected $sink = null; // Output filters
+ protected $lastTime = 0;
+ protected $pageCountLast = 0;
+ protected $revCountLast = 0;
+
+ protected $outputTypes = [];
+ protected $filterTypes = [];
+
+ protected $ID = 0;
+
+ /**
+ * The dependency-injected database to use.
+ *
+ * @var IDatabase|null
+ *
+ * @see self::setDB
+ */
+ protected $forcedDb = null;
+
+ /** @var LoadBalancer */
+ protected $lb;
+
+ // @todo Unused?
+ private $stubText = false; // include rev_text_id instead of text; for 2-pass dump
+
+ /**
+ * @param array|null $args For backward compatibility
+ */
+ function __construct( $args = null ) {
+ parent::__construct();
+ $this->stderr = fopen( "php://stderr", "wt" );
+
+ // Built-in output and filter plugins
+ $this->registerOutput( 'file', DumpFileOutput::class );
+ $this->registerOutput( 'gzip', DumpGZipOutput::class );
+ $this->registerOutput( 'bzip2', DumpBZip2Output::class );
+ $this->registerOutput( 'dbzip2', DumpDBZip2Output::class );
+ $this->registerOutput( '7zip', Dump7ZipOutput::class );
+
+ $this->registerFilter( 'latest', DumpLatestFilter::class );
+ $this->registerFilter( 'notalk', DumpNotalkFilter::class );
+ $this->registerFilter( 'namespace', DumpNamespaceFilter::class );
+
+ // These three can be specified multiple times
+ $this->addOption( 'plugin', 'Load a dump plugin class. Specify as <class>[:<file>].',
+ false, true, false, true );
+ $this->addOption( 'output', 'Begin a filtered output stream; Specify as <type>:<file>. ' .
+ '<type>s: file, gzip, bzip2, 7zip, dbzip2', false, true, false, true );
+ $this->addOption( 'filter', 'Add a filter on an output branch. Specify as ' .
+ '<type>[:<options>]. <types>s: latest, notalk, namespace', false, true, false, true );
+ $this->addOption( 'report', 'Report position and speed after every n pages processed. ' .
+ 'Default: 100.', false, true );
+ $this->addOption( 'server', 'Force reading from MySQL server', false, true );
+ $this->addOption( '7ziplevel', '7zip compression level for all 7zip outputs. Used for ' .
+ '-mx option to 7za command.', false, true );
+
+ if ( $args ) {
+ // Args should be loaded and processed so that dump() can be called directly
+ // instead of execute()
+ $this->loadWithArgv( $args );
+ $this->processOptions();
+ }
+ }
+
+ /**
+ * @param string $name
+ * @param string $class Name of output filter plugin class
+ */
+ function registerOutput( $name, $class ) {
+ $this->outputTypes[$name] = $class;
+ }
+
+ /**
+ * @param string $name
+ * @param string $class Name of filter plugin class
+ */
+ function registerFilter( $name, $class ) {
+ $this->filterTypes[$name] = $class;
+ }
+
+ /**
+ * Load a plugin and register it
+ *
+ * @param string $class Name of plugin class; must have a static 'register'
+ * method that takes a BackupDumper as a parameter.
+ * @param string $file Full or relative path to the PHP file to load, or empty
+ */
+ function loadPlugin( $class, $file ) {
+ if ( $file != '' ) {
+ require_once $file;
+ }
+ $register = [ $class, 'register' ];
+ $register( $this );
+ }
+
+ function execute() {
+ throw new MWException( 'execute() must be overridden in subclasses' );
+ }
+
+ /**
+ * Processes arguments and sets $this->$sink accordingly
+ */
+ function processOptions() {
+ $sink = null;
+ $sinks = [];
+
+ $options = $this->orderedOptions;
+ foreach ( $options as $arg ) {
+ $opt = $arg[0];
+ $param = $arg[1];
+
+ switch ( $opt ) {
+ case 'plugin':
+ $val = explode( ':', $param );
+
+ if ( count( $val ) === 1 ) {
+ $this->loadPlugin( $val[0], '' );
+ } elseif ( count( $val ) === 2 ) {
+ $this->loadPlugin( $val[0], $val[1] );
+ } else {
+ $this->fatalError( 'Invalid plugin parameter' );
+ return;
+ }
+
+ break;
+ case 'output':
+ $split = explode( ':', $param, 2 );
+ if ( count( $split ) !== 2 ) {
+ $this->fatalError( 'Invalid output parameter' );
+ }
+ list( $type, $file ) = $split;
+ if ( !is_null( $sink ) ) {
+ $sinks[] = $sink;
+ }
+ if ( !isset( $this->outputTypes[$type] ) ) {
+ $this->fatalError( "Unrecognized output sink type '$type'" );
+ }
+ $class = $this->outputTypes[$type];
+ if ( $type === "7zip" ) {
+ $sink = new $class( $file, intval( $this->getOption( '7ziplevel' ) ) );
+ } else {
+ $sink = new $class( $file );
+ }
+
+ break;
+ case 'filter':
+ if ( is_null( $sink ) ) {
+ $sink = new DumpOutput();
+ }
+
+ $split = explode( ':', $param );
+ $key = $split[0];
+
+ if ( !isset( $this->filterTypes[$key] ) ) {
+ $this->fatalError( "Unrecognized filter type '$key'" );
+ }
+
+ $type = $this->filterTypes[$key];
+
+ if ( count( $split ) === 1 ) {
+ $filter = new $type( $sink );
+ } elseif ( count( $split ) === 2 ) {
+ $filter = new $type( $sink, $split[1] );
+ } else {
+ $this->fatalError( 'Invalid filter parameter' );
+ }
+
+ // references are lame in php...
+ unset( $sink );
+ $sink = $filter;
+
+ break;
+ }
+ }
+
+ if ( $this->hasOption( 'report' ) ) {
+ $this->reportingInterval = intval( $this->getOption( 'report' ) );
+ }
+
+ if ( $this->hasOption( 'server' ) ) {
+ $this->server = $this->getOption( 'server' );
+ }
+
+ if ( is_null( $sink ) ) {
+ $sink = new DumpOutput();
+ }
+ $sinks[] = $sink;
+
+ if ( count( $sinks ) > 1 ) {
+ $this->sink = new DumpMultiWriter( $sinks );
+ } else {
+ $this->sink = $sink;
+ }
+ }
+
+ function dump( $history, $text = WikiExporter::TEXT ) {
+ # Notice messages will foul up your XML output even if they're
+ # relatively harmless.
+ if ( ini_get( 'display_errors' ) ) {
+ ini_set( 'display_errors', 'stderr' );
+ }
+
+ $this->initProgress( $history );
+
+ $db = $this->backupDb();
+ $exporter = new WikiExporter( $db, $history, WikiExporter::STREAM, $text );
+ $exporter->dumpUploads = $this->dumpUploads;
+ $exporter->dumpUploadFileContents = $this->dumpUploadFileContents;
+
+ $wrapper = new ExportProgressFilter( $this->sink, $this );
+ $exporter->setOutputSink( $wrapper );
+
+ if ( !$this->skipHeader ) {
+ $exporter->openStream();
+ }
+ # Log item dumps: all or by range
+ if ( $history & WikiExporter::LOGS ) {
+ if ( $this->startId || $this->endId ) {
+ $exporter->logsByRange( $this->startId, $this->endId );
+ } else {
+ $exporter->allLogs();
+ }
+ } elseif ( is_null( $this->pages ) ) {
+ # Page dumps: all or by page ID range
+ if ( $this->startId || $this->endId ) {
+ $exporter->pagesByRange( $this->startId, $this->endId, $this->orderRevs );
+ } elseif ( $this->revStartId || $this->revEndId ) {
+ $exporter->revsByRange( $this->revStartId, $this->revEndId );
+ } else {
+ $exporter->allPages();
+ }
+ } else {
+ # Dump of specific pages
+ $exporter->pagesByName( $this->pages );
+ }
+
+ if ( !$this->skipFooter ) {
+ $exporter->closeStream();
+ }
+
+ $this->report( true );
+ }
+
+ /**
+ * Initialise starting time and maximum revision count.
+ * We'll make ETA calculations based an progress, assuming relatively
+ * constant per-revision rate.
+ * @param int $history WikiExporter::CURRENT or WikiExporter::FULL
+ */
+ function initProgress( $history = WikiExporter::FULL ) {
+ $table = ( $history == WikiExporter::CURRENT ) ? 'page' : 'revision';
+ $field = ( $history == WikiExporter::CURRENT ) ? 'page_id' : 'rev_id';
+
+ $dbr = $this->forcedDb;
+ if ( $this->forcedDb === null ) {
+ $dbr = wfGetDB( DB_REPLICA );
+ }
+ $this->maxCount = $dbr->selectField( $table, "MAX($field)", '', __METHOD__ );
+ $this->startTime = microtime( true );
+ $this->lastTime = $this->startTime;
+ $this->ID = getmypid();
+ }
+
+ /**
+ * @todo Fixme: the --server parameter is currently not respected, as it
+ * doesn't seem terribly easy to ask the load balancer for a particular
+ * connection by name.
+ * @return IDatabase
+ */
+ function backupDb() {
+ if ( $this->forcedDb !== null ) {
+ return $this->forcedDb;
+ }
+
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $this->lb = $lbFactory->newMainLB();
+ $db = $this->lb->getConnection( DB_REPLICA, 'dump' );
+
+ // Discourage the server from disconnecting us if it takes a long time
+ // to read out the big ol' batch query.
+ $db->setSessionOptions( [ 'connTimeout' => 3600 * 24 ] );
+
+ return $db;
+ }
+
+ /**
+ * Force the dump to use the provided database connection for database
+ * operations, wherever possible.
+ *
+ * @param IDatabase|null $db (Optional) the database connection to use. If null, resort to
+ * use the globally provided ways to get database connections.
+ */
+ function setDB( IDatabase $db = null ) {
+ parent::setDB( $db );
+ $this->forcedDb = $db;
+ }
+
+ function __destruct() {
+ if ( isset( $this->lb ) ) {
+ $this->lb->closeAll();
+ }
+ }
+
+ function backupServer() {
+ global $wgDBserver;
+
+ return $this->server
+ ? $this->server
+ : $wgDBserver;
+ }
+
+ function reportPage() {
+ $this->pageCount++;
+ }
+
+ function revCount() {
+ $this->revCount++;
+ $this->report();
+ }
+
+ function report( $final = false ) {
+ if ( $final xor ( $this->revCount % $this->reportingInterval == 0 ) ) {
+ $this->showReport();
+ }
+ }
+
+ function showReport() {
+ if ( $this->reporting ) {
+ $now = wfTimestamp( TS_DB );
+ $nowts = microtime( true );
+ $deltaAll = $nowts - $this->startTime;
+ $deltaPart = $nowts - $this->lastTime;
+ $this->pageCountPart = $this->pageCount - $this->pageCountLast;
+ $this->revCountPart = $this->revCount - $this->revCountLast;
+
+ if ( $deltaAll ) {
+ $portion = $this->revCount / $this->maxCount;
+ $eta = $this->startTime + $deltaAll / $portion;
+ $etats = wfTimestamp( TS_DB, intval( $eta ) );
+ $pageRate = $this->pageCount / $deltaAll;
+ $revRate = $this->revCount / $deltaAll;
+ } else {
+ $pageRate = '-';
+ $revRate = '-';
+ $etats = '-';
+ }
+ if ( $deltaPart ) {
+ $pageRatePart = $this->pageCountPart / $deltaPart;
+ $revRatePart = $this->revCountPart / $deltaPart;
+ } else {
+ $pageRatePart = '-';
+ $revRatePart = '-';
+ }
+ $this->progress( sprintf(
+ "%s: %s (ID %d) %d pages (%0.1f|%0.1f/sec all|curr), "
+ . "%d revs (%0.1f|%0.1f/sec all|curr), ETA %s [max %d]",
+ $now, wfWikiID(), $this->ID, $this->pageCount, $pageRate,
+ $pageRatePart, $this->revCount, $revRate, $revRatePart, $etats,
+ $this->maxCount
+ ) );
+ $this->lastTime = $nowts;
+ $this->revCountLast = $this->revCount;
+ }
+ }
+
+ function progress( $string ) {
+ if ( $this->reporting ) {
+ fwrite( $this->stderr, $string . "\n" );
+ }
+ }
+}
"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"
]
}
密执安 密歇根
密西根 密歇根
紐澤西 新泽西
+奧勒岡 俄勒冈
蒙特婁 蒙特利尔
千里達及托巴哥 特立尼达和多巴哥
千里達 特立尼达
主機板 主板
網際網絡 互联网
原始碼 源代码
-螢幕 屏幕
螢屏 荧屏
解像度 分辨率
IP位址 IP地址
希拉莉 希拉里
文翠珊 特蕾莎·梅
德蕾莎·梅伊 特蕾莎·梅
+馬拉度納 马拉多纳
+馬勒當拿 马拉多纳
麻薩諸塞 马萨诸塞
東南亞國家協會 东南亚国家联盟
獨立國協 独联体
烏龍麵 乌冬面
披索 比索
真人騷 真人秀
+帕運會 残奥会
+帕拉林匹克 残疾人奥林匹克
+傷殘奧林匹克 残疾人奥林匹克
愛荷華 艾奧瓦
爱荷华 艾奧瓦
得克萨斯 德克薩斯
+奧勒岡 俄勒岡
蒙特婁 蒙特利爾
紐賓士域 紐賓士域
加泰隆尼亞 加泰羅尼亞
链接 連結
分辨率 解像度
解析度 解像度
+服务器 伺服器
智慧卡 智能卡
晶元 晶片
芯片 晶片
晶体管 電晶體
源代码 原始碼
IP地址 IP位址
-屏幕 螢幕
荧屏 螢屏
版权信息 版權資訊
信息时代 資訊時代
翁山蘇姬 昂山素姬
德蕾莎·梅伊 文翠珊
特蕾莎·梅 文翠珊
+马拉多纳 馬勒當拿
+馬拉度納 馬勒當拿
西洋棋 國際象棋
隐私 私隱
隱私 私隱
塑料袋 膠袋
烏龍麵 烏冬麵
真人秀 真人騷
+帕運會 殘奧會
+帕拉林匹克 傷殘奧林匹克
+残疾人奥林匹克 傷殘奧林匹克
曾运乾 曾运乾
乾贵士 乾贵士
乾东 乾东
+乾顺 乾顺
柳诒徵 柳诒徵
於夫罗 於夫罗
於梨华 於梨华
觔斗 斤斗
穀阳 穀阳
伊東豊雄 伊东丰雄
+峯會 峰会
+頂峯 顶峰
+巔峯 巅峰
+拚弃 拚弃
+拚棄 拚弃
+拚却 拚却
+拚卻 拚却
+满拚自尽 满拚自尽
+滿拚自盡 满拚自尽
+拚生尽死 拚生尽死
+拚生盡死 拚生尽死
晶体管 電晶體
IP地址 IP位址
解像度 解析度
-屏幕 螢幕
荧屏 螢屏
版权信息 版權資訊
航天器 太空飛行器
格莱美奖 葛萊美獎
乔布斯 賈伯斯
波里活 寶萊塢
+马拉多纳 馬拉度納
+馬勒當拿 馬拉度納
库尔德族 庫德族
库尔德人 庫德人
行人路 人行道
触摸屏 觸控螢幕
乌冬面 烏龍麵
真人騷 真人秀
+残奥会 帕運會
+殘奧會 帕運會
+残疾人奥林匹克 帕拉林匹克
+傷殘奧林匹克 帕拉林匹克
‘ 『
’ 』
’s ’s
+’m ’m
+’t ’t
+’re ’re
手塚治虫 手塚治虫
寇仇 寇讎
往日无仇 往日無讎
簡筑翎 簡筑翎
楊雅筑 楊雅筑
尸羅精舍 尸羅精舍
+拚舍 拚捨
騰格里 騰格里
進制 進制
強制 強制
U+05563啣|U+08854衔|
U+055AB喫|U+05403吃|
U+055C1嗁|U+0557C啼|
-U+05605嘅|U+06168慨|
U+05611嘑|U+0547C呼|
U+05620嘠|U+0560E嘎|
U+05637嘷|U+055E5嗥|
出乖弄醜
出乖露醜
獲匪其醜
+長得醜
乙丑
丁丑
己丑
括髮
髡髮
鵠髮
-截髮
解髮佯狂
淨髮
噙齒戴髮
犖确
磽确
确瘠
-拚捨
廣捨
齊王捨牛
捨墮
這麼幹
幹這
幹仗
+包幹
李連杰
周杰
杰倫
挌鬥
好鬥
鬥合
-æ\8b\9a鬥
+æ\8b¼鬥
兩虎共鬥
兩鼠鬥穴
鬥犀臺
穩健的台風
台風獎
電視台風
+舞台風格
足球台
網球台
合府上
溫洛克期
科尼亞克期
馬斯垂克期
-滿拚自盡
-拚生盡死
-拚卻
-拚老命
-拚絕
成於思
單單於
名單於
繫鞋帶
繫船
繫着
-重回
+重回 #分詞
+收回
挑大樑
扛大樑
后豐
帝后臺
紅后假說
尊后
+后姓
+電影後
+封為后
+天神之后
+夏后氏
前往
新井里美
樗里子
湧水
高涌泉
涌水塘
-后姓
計劃
党姓
党家
煙臺
太醜
御製
-電影後
-封為后
皮托管
白面包青天
-天神之后
你誇
誇你
誇我
自誇
誇稱
誇讚
+像讚
黎克特制
筆桿
袋桿
* @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
--- /dev/null
+### Format of this file
+#
+# The top-level keys are module names (as registered in Resources.php).
+# Each top-level key holds a resource descriptor that must have one of
+# the following `type` values:
+#
+# - `tar`: For tarball archive (may be gzip-compressed).
+# - `file: For a plain file.
+# - `multi-file`: For multiple plain files.
+#
+### 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/dist/oojs.jquery.js:
+ package/AUTHORS.txt:
+ 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:
+ # Main stuff
+ package/dist/oojs-ui-core.js{,.map.json}:
+ package/dist/oojs-ui-core-{wikimediaui,apex}.css:
+ package/dist/oojs-ui-widgets.js{,.map.json}:
+ package/dist/oojs-ui-widgets-{wikimediaui,apex}.css:
+ package/dist/oojs-ui-toolbars.js{,.map.json}:
+ package/dist/oojs-ui-toolbars-{wikimediaui,apex}.css:
+ package/dist/oojs-ui-windows.js{,.map.json}:
+ package/dist/oojs-ui-windows-{wikimediaui,apex}.css:
+ package/dist/oojs-ui-{wikimediaui,apex}.js{,.map.json}:
+ package/dist/i18n:
+ package/dist/images:
+ # WikimediaUI theme
+ package/dist/themes/wikimediaui/images/icons/*.{svg,png}: themes/wikimediaui/images/icons
+ package/dist/themes/wikimediaui/images/indicators/*.{svg,png}: themes/wikimediaui/images/indicators
+ package/dist/themes/wikimediaui/images/textures/*.{gif,svg}: themes/wikimediaui/images/textures
+ package/src/themes/wikimediaui/*.json: themes/wikimediaui
+ package/dist/wikimedia-ui-base.less:
+ # Apex theme (icons, indicators, and textures)
+ package/src/themes/apex/*.json: themes/apex
+ # Misc stuff
+ package/dist/AUTHORS.txt:
+ 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
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Maintenance
+ */
+
+require_once __DIR__ . '/../Maintenance.php';
+
+/**
+ * Manage foreign resources registered with ResourceLoader.
+ *
+ * @ingroup Maintenance
+ * @since 1.32
+ */
+class ManageForeignResources extends Maintenance {
+ private $defaultAlgo = 'sha384';
+ private $tmpParentDir;
+ private $action;
+ private $failAfterOutput = false;
+
+ public function __construct() {
+ global $IP;
+ parent::__construct();
+ $this->addDescription( <<<TEXT
+Manage foreign resources registered with ResourceLoader.
+
+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, 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( 'verbose', 'Be verbose', false, false, 'v' );
+
+ // Use a directory in $IP instead of wfTempDir() because
+ // PHP's rename() does not work across file systems.
+ $this->tmpParentDir = "{$IP}/resources/tmp";
+ }
+
+ public function execute() {
+ global $IP;
+ $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 !== 'all' && $moduleName !== $module ) {
+ continue;
+ }
+ $this->verbose( "\n### {$moduleName}\n\n" );
+ $destDir = "{$IP}/resources/lib/$moduleName";
+
+ 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" );
+ }
+
+ $this->verbose( "... preparing {$this->tmpParentDir}\n" );
+ wfRecursiveRemoveDir( $this->tmpParentDir );
+ if ( !wfMkdirParents( $this->tmpParentDir ) ) {
+ $this->fatalError( "Unable to create {$this->tmpParentDir}" );
+ }
+
+ 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'" );
+ }
+ }
+
+ $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}"
+ );
+ }
+ }
+ return $data;
+ }
+
+ 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 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 );
+ }
+ }
+ }
+
+ 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 );
+ $p = new PharData( $tmpFile );
+ $tmpDir = "{$this->tmpParentDir}/$moduleName";
+ $p->extractTo( $tmpDir );
+ unset( $data, $p );
+
+ if ( $info['dest'] === null ) {
+ // Default: Replace the entire directory
+ $toCopy = [ $tmpDir => $destDir ];
+ } else {
+ // 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->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 ) ) {
+ $this->fatalError( "Could not move $from to $to." );
+ }
+ }
+ }
+ }
+
+ private function verbose( $text ) {
+ if ( $this->hasOption( 'verbose' ) ) {
+ $this->output( $text );
+ }
+ }
+
+ private function cleanUp() {
+ wfRecursiveRemoveDir( $this->tmpParentDir );
+ }
+
+ protected function fatalError( $msg, $exitCode = 1 ) {
+ $this->cleanUp();
+ parent::fatalError( $msg, $exitCode );
+ }
+
+ /**
+ * Basic YAML parser.
+ *
+ * Supports only string or object values, and 2 spaces indentation.
+ *
+ * @todo Just ship symfony/yaml.
+ * @param string $input
+ * @return array
+ */
+ private function parseBasicYaml( $input ) {
+ $lines = explode( "\n", $input );
+ $root = [];
+ $stack = [ &$root ];
+ $prev = 0;
+ foreach ( $lines as $i => $text ) {
+ $line = $i + 1;
+ $trimmed = ltrim( $text, ' ' );
+ if ( $trimmed === '' || $trimmed[0] === '#' ) {
+ continue;
+ }
+ $indent = strlen( $text ) - strlen( $trimmed );
+ if ( $indent % 2 !== 0 ) {
+ throw new Exception( __METHOD__ . ": Odd indentation on line $line." );
+ }
+ $depth = $indent === 0 ? 0 : ( $indent / 2 );
+ if ( $depth < $prev ) {
+ // Close previous branches we can't re-enter
+ array_splice( $stack, $depth + 1 );
+ }
+ if ( !array_key_exists( $depth, $stack ) ) {
+ throw new Exception( __METHOD__ . ": Too much indentation on line $line." );
+ }
+ if ( strpos( $trimmed, ':' ) === false ) {
+ throw new Exception( __METHOD__ . ": Missing colon on line $line." );
+ }
+ $dest =& $stack[ $depth ];
+ if ( $dest === null ) {
+ // Promote from null to object
+ $dest = [];
+ }
+ list( $key, $val ) = explode( ':', $trimmed, 2 );
+ $val = ltrim( $val, ' ' );
+ if ( $val !== '' ) {
+ // Add string
+ $dest[ $key ] = $val;
+ } else {
+ // Add null (may become an object later)
+ $val = null;
+ $stack[] = &$val;
+ $dest[ $key ] = &$val;
+ }
+ $prev = $depth;
+ unset( $dest, $val );
+ }
+ return $root;
+ }
+}
+
+$maintClass = ManageForeignResources::class;
+require_once RUN_MAINTENANCE_IF_MAIN;
+++ /dev/null
-#!/bin/bash -eu
-
-# This script generates a commit that updates our copy of OOjs
-
-if [ -n "${2:-}" ]
-then
- # Too many parameters
- echo >&2 "Usage: $0 [<version>]"
- exit 1
-fi
-
-REPO_DIR=$(cd "$(dirname $0)/../.."; pwd) # Root dir of the git repo working tree
-TARGET_DIR="resources/lib/oojs" # Destination relative to the root of the repo
-NPM_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'update-oojs') # e.g. /tmp/update-oojs.rI0I5Vir
-
-# Prepare working tree
-cd "$REPO_DIR"
-git reset -- $TARGET_DIR
-git checkout -- $TARGET_DIR
-git fetch origin
-git checkout -B upstream-oojs origin/master
-
-# Fetch upstream version
-cd $NPM_DIR
-if [ -n "${1:-}" ]
-then
- npm install "oojs@$1"
-else
- npm install oojs
-fi
-
-OOJS_VERSION=$(node -e 'console.log(require("./node_modules/oojs/package.json").version);')
-if [ "$OOJS_VERSION" == "" ]
-then
- echo 'Could not find OOjs version'
- exit 1
-fi
-
-# Copy file(s)
-rsync --force ./node_modules/oojs/dist/oojs.jquery.js "$REPO_DIR/$TARGET_DIR"
-rsync --force ./node_modules/oojs/AUTHORS.txt "$REPO_DIR/$TARGET_DIR"
-rsync --force ./node_modules/oojs/LICENSE-MIT "$REPO_DIR/$TARGET_DIR"
-rsync --force ./node_modules/oojs/README.md "$REPO_DIR/$TARGET_DIR"
-
-# Clean up temporary area
-rm -rf "$NPM_DIR"
-
-# Generate commit
-cd $REPO_DIR
-
-COMMITMSG=$(cat <<END
-Update OOjs to v$OOJS_VERSION
-
-Release notes:
- https://gerrit.wikimedia.org/r/plugins/gitiles/oojs/core/+/v$OOJS_VERSION/History.md
-END
-)
-
-# Stage deletion, modification and creation of files. Then commit.
-git add --update $TARGET_DIR
-git add $TARGET_DIR
-git commit -m "$COMMITMSG"
+++ /dev/null
-#!/bin/bash -eu
-
-# This script generates a commit that updates our copy of OOUI
-
-if [ -n "${2:-}" ]
-then
- # Too many parameters
- echo >&2 "Usage: $0 [<version>]"
- exit 1
-fi
-
-REPO_DIR=$(cd "$(dirname $0)/../.."; pwd) # Root dir of the git repo working tree
-TARGET_DIR="resources/lib/oojs-ui" # Destination relative to the root of the repo
-NPM_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'update-ooui') # e.g. /tmp/update-ooui.rI0I5Vir
-
-# Prepare working tree
-cd "$REPO_DIR"
-git reset composer.json
-git checkout composer.json
-git reset -- $TARGET_DIR
-git checkout -- $TARGET_DIR
-git fetch origin
-git checkout -B upstream-ooui origin/master
-
-# Fetch upstream version
-cd $NPM_DIR
-if [ -n "${1:-}" ]
-then
- npm install "oojs-ui@$1"
-else
- npm install oojs-ui
-fi
-
-OOUI_VERSION=$(node -e 'console.log(require("./node_modules/oojs-ui/package.json").version);')
-if [ "$OOUI_VERSION" == "" ]
-then
- echo 'Could not find OOUI version'
- exit 1
-fi
-
-# Copy files, picking the necessary ones from source and distribution
-rm -r "$REPO_DIR/$TARGET_DIR"
-
-# Core and thematic code and styling
-mkdir -p "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-core.js{,.map.json} "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-core-{wikimediaui,apex}.css "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-widgets.js{,.map.json} "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-widgets-{wikimediaui,apex}.css "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-toolbars.js{,.map.json} "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-toolbars-{wikimediaui,apex}.css "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-windows.js{,.map.json} "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-windows-{wikimediaui,apex}.css "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-{wikimediaui,apex}.js{,.map.json} "$REPO_DIR/$TARGET_DIR"
-
-# i18n
-mkdir -p "$REPO_DIR/$TARGET_DIR/i18n"
-cp -R ./node_modules/oojs-ui/dist/i18n "$REPO_DIR/$TARGET_DIR"
-
-# Core images (currently two .cur files)
-mkdir -p "$REPO_DIR/$TARGET_DIR/images"
-cp -R ./node_modules/oojs-ui/dist/images "$REPO_DIR/$TARGET_DIR"
-
-# WikimediaUI theme icons, indicators, and textures
-mkdir -p "$REPO_DIR/$TARGET_DIR/themes/wikimediaui/images/icons"
-cp ./node_modules/oojs-ui/dist/themes/wikimediaui/images/icons/*.{svg,png} "$REPO_DIR/$TARGET_DIR/themes/wikimediaui/images/icons"
-mkdir -p "$REPO_DIR/$TARGET_DIR/themes/wikimediaui/images/indicators"
-cp ./node_modules/oojs-ui/dist/themes/wikimediaui/images/indicators/*.{svg,png} "$REPO_DIR/$TARGET_DIR/themes/wikimediaui/images/indicators"
-mkdir -p "$REPO_DIR/$TARGET_DIR/themes/wikimediaui/images/textures"
-cp ./node_modules/oojs-ui/dist/themes/wikimediaui/images/textures/*.{gif,svg} "$REPO_DIR/$TARGET_DIR/themes/wikimediaui/images/textures"
-
-cp ./node_modules/oojs-ui/src/themes/wikimediaui/*.json "$REPO_DIR/$TARGET_DIR/themes/wikimediaui"
-
-# Apex theme icons, indicators, and textures
-mkdir -p "$REPO_DIR/$TARGET_DIR/themes/apex"
-cp ./node_modules/oojs-ui/src/themes/apex/*.json "$REPO_DIR/$TARGET_DIR/themes/apex"
-
-# WikimediaUI LESS variables for sharing
-cp ./node_modules/oojs-ui/dist/wikimedia-ui-base.less "$REPO_DIR/$TARGET_DIR"
-
-# Misc stuff
-cp ./node_modules/oojs-ui/dist/AUTHORS.txt "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/History.md "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/LICENSE-MIT "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/README.md "$REPO_DIR/$TARGET_DIR"
-
-# Clean up temporary area
-rm -rf "$NPM_DIR"
-
-# Generate commit
-cd $REPO_DIR
-
-COMMITMSG=$(cat <<END
-Update OOUI to v$OOUI_VERSION
-
-Release notes:
- https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/History.md;v$OOUI_VERSION
-END
-)
-
-# Update composer.json as well
-composer require oojs/oojs-ui $OOUI_VERSION --no-update
-
-# Stage deletion, modification and creation of files. Then commit.
-git add --update $TARGET_DIR
-git add $TARGET_DIR
-git add composer.json
-git commit -m "$COMMITMSG"
-- 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' => [ 'mobile', 'desktop' ],
],
'jquery.async' => [
- 'scripts' => 'resources/lib/jquery/jquery.async.js',
+ 'scripts' => 'resources/lib/jquery.async.js',
],
'jquery.byteLength' => [
'scripts' => 'resources/src/jquery/jquery.byteLength.js',
'dependencies' => 'mediawiki.jqueryMsg',
],
'jquery.cookie' => [
- 'scripts' => 'resources/lib/jquery/jquery.cookie.js',
+ 'scripts' => 'resources/lib/jquery.cookie.js',
'targets' => [ 'desktop', 'mobile' ],
],
'jquery.form' => [
- 'scripts' => 'resources/lib/jquery/jquery.form.js',
+ 'scripts' => 'resources/lib/jquery.form.js',
],
'jquery.fullscreen' => [
- 'scripts' => 'resources/lib/jquery/jquery.fullscreen.js',
+ 'scripts' => 'resources/lib/jquery.fullscreen.js',
],
'jquery.getAttrs' => [
'targets' => [ 'desktop', 'mobile' ],
'targets' => [ 'desktop', 'mobile' ],
],
'jquery.hoverIntent' => [
- 'scripts' => 'resources/lib/jquery/jquery.hoverIntent.js',
+ 'scripts' => 'resources/lib/jquery.hoverIntent.js',
],
'jquery.i18n' => [
'scripts' => [
],
'jquery.localize' => [
'scripts' => 'resources/src/jquery/jquery.localize.js',
+ 'deprecated' => 'Please use "jquery.i18n" instead.',
],
'jquery.makeCollapsible' => [
'dependencies' => [ 'jquery.makeCollapsible.styles' ],
'targets' => [ 'desktop', 'mobile' ],
],
'jquery.mockjax' => [
- 'scripts' => 'resources/lib/jquery/jquery.mockjax.js',
+ 'scripts' => 'resources/lib/jquery.mockjax.js',
],
'jquery.mw-jump' => [
'scripts' => 'resources/src/jquery/jquery.mw-jump.js',
],
'jquery.jStorage' => [
'deprecated' => 'Please use "mediawiki.storage" instead.',
- 'scripts' => 'resources/lib/jquery/jquery.jStorage.js',
+ 'scripts' => 'resources/lib/jquery.jStorage.js',
],
'jquery.suggestions' => [
'targets' => [ 'desktop', 'mobile' ],
'targets' => [ 'mobile', 'desktop' ],
],
'jquery.throttle-debounce' => [
- 'scripts' => 'resources/lib/jquery/jquery.ba-throttle-debounce.js',
+ 'scripts' => 'resources/lib/jquery.ba-throttle-debounce.js',
'targets' => [ 'desktop', 'mobile' ],
],
'jquery.xmldom' => [
- 'scripts' => 'resources/lib/jquery/jquery.xmldom.js',
+ 'scripts' => 'resources/lib/jquery.xmldom.js',
],
/* jQuery Tipsy */
],
'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',
'oojs-ui-widgets' => [
'class' => ResourceLoaderOOUIFileModule::class,
'scripts' => 'resources/lib/oojs-ui/oojs-ui-widgets.js',
+ 'themeStyles' => 'widgets',
'dependencies' => [
'oojs-ui-core',
- 'oojs-ui-widgets.styles',
'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-editing-advanced',
--- /dev/null
+/*
+ * jQuery Asynchronous Plugin 1.0
+ *
+ * Copyright (c) 2008 Vincent Robert (genezys.net)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ */
+(function($){
+
+// opts.delay : (default 10) delay between async call in ms
+// opts.bulk : (default 500) delay during which the loop can continue synchronously without yielding the CPU
+// opts.test : (default true) function to test in the while test part
+// opts.loop : (default empty) function to call in the while loop part
+// opts.end : (default empty) function to call at the end of the while loop
+$.whileAsync = function(opts) {
+ var delay = Math.abs(opts.delay) || 10,
+ bulk = isNaN(opts.bulk) ? 500 : Math.abs(opts.bulk),
+ test = opts.test || function(){ return true; },
+ loop = opts.loop || function(){},
+ end = opts.end || function(){};
+
+ (function(){
+
+ var t = false,
+ begin = new Date();
+
+ while( t = test() ) {
+ loop();
+ if( bulk === 0 || (new Date() - begin) > bulk ) {
+ break;
+ }
+ }
+ if( t ) {
+ setTimeout(arguments.callee, delay);
+ }
+ else {
+ end();
+ }
+
+ })();
+};
+
+// opts.delay : (default 10) delay between async call in ms
+// opts.bulk : (default 500) delay during which the loop can continue synchronously without yielding the CPU
+// opts.loop : (default empty) function to call in the each loop part, signature: function(index, value) this = value
+// opts.end : (default empty) function to call at the end of the each loop
+$.eachAsync = function(array, opts) {
+ var i = 0,
+ l = array.length,
+ loop = opts.loop || function(){};
+
+ $.whileAsync(
+ $.extend(opts, {
+ test: function() { return i < l; },
+ loop: function() {
+ var val = array[i];
+ return loop.call(val, i++, val);
+ }
+ })
+ );
+};
+
+$.fn.eachAsync = function(opts) {
+ $.eachAsync(this, opts);
+ return this;
+}
+
+})(jQuery);
\ No newline at end of file
--- /dev/null
+/*!
+ * jQuery throttle / debounce - v1.1 - 3/7/2010
+ * http://benalman.com/projects/jquery-throttle-debounce-plugin/
+ *
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+
+// Script: jQuery throttle / debounce: Sometimes, less is more!
+//
+// *Version: 1.1, Last updated: 3/7/2010*
+//
+// Project Home - http://benalman.com/projects/jquery-throttle-debounce-plugin/
+// GitHub - http://github.com/cowboy/jquery-throttle-debounce/
+// Source - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.js
+// (Minified) - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.min.js (0.7kb)
+//
+// About: License
+//
+// Copyright (c) 2010 "Cowboy" Ben Alman,
+// Dual licensed under the MIT and GPL licenses.
+// http://benalman.com/about/license/
+//
+// About: Examples
+//
+// These working examples, complete with fully commented code, illustrate a few
+// ways in which this plugin can be used.
+//
+// Throttle - http://benalman.com/code/projects/jquery-throttle-debounce/examples/throttle/
+// Debounce - http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/
+//
+// About: Support and Testing
+//
+// Information about what version or versions of jQuery this plugin has been
+// tested with, what browsers it has been tested in, and where the unit tests
+// reside (so you can test it yourself).
+//
+// jQuery Versions - none, 1.3.2, 1.4.2
+// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome 4-5, Opera 9.6-10.1.
+// Unit Tests - http://benalman.com/code/projects/jquery-throttle-debounce/unit/
+//
+// About: Release History
+//
+// 1.1 - (3/7/2010) Fixed a bug in <jQuery.throttle> where trailing callbacks
+// executed later than they should. Reworked a fair amount of internal
+// logic as well.
+// 1.0 - (3/6/2010) Initial release as a stand-alone project. Migrated over
+// from jquery-misc repo v0.4 to jquery-throttle repo v1.0, added the
+// no_trailing throttle parameter and debounce functionality.
+//
+// Topic: Note for non-jQuery users
+//
+// jQuery isn't actually required for this plugin, because nothing internal
+// uses any jQuery methods or properties. jQuery is just used as a namespace
+// under which these methods can exist.
+//
+// Since jQuery isn't actually required for this plugin, if jQuery doesn't exist
+// when this plugin is loaded, the method described below will be created in
+// the `Cowboy` namespace. Usage will be exactly the same, but instead of
+// $.method() or jQuery.method(), you'll need to use Cowboy.method().
+
+(function(window,undefined){
+ '$:nomunge'; // Used by YUI compressor.
+
+ // Since jQuery really isn't required for this plugin, use `jQuery` as the
+ // namespace only if it already exists, otherwise use the `Cowboy` namespace,
+ // creating it if necessary.
+ var $ = window.jQuery || window.Cowboy || ( window.Cowboy = {} ),
+
+ // Internal method reference.
+ jq_throttle;
+
+ // Method: jQuery.throttle
+ //
+ // Throttle execution of a function. Especially useful for rate limiting
+ // execution of handlers on events like resize and scroll. If you want to
+ // rate-limit execution of a function to a single time, see the
+ // <jQuery.debounce> method.
+ //
+ // In this visualization, | is a throttled-function call and X is the actual
+ // callback execution:
+ //
+ // > Throttled with `no_trailing` specified as false or unspecified:
+ // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
+ // > X X X X X X X X X X X X
+ // >
+ // > Throttled with `no_trailing` specified as true:
+ // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
+ // > X X X X X X X X X X
+ //
+ // Usage:
+ //
+ // > var throttled = jQuery.throttle( delay, [ no_trailing, ] callback );
+ // >
+ // > jQuery('selector').bind( 'someevent', throttled );
+ // > jQuery('selector').unbind( 'someevent', throttled );
+ //
+ // This also works in jQuery 1.4+:
+ //
+ // > jQuery('selector').bind( 'someevent', jQuery.throttle( delay, [ no_trailing, ] callback ) );
+ // > jQuery('selector').unbind( 'someevent', callback );
+ //
+ // Arguments:
+ //
+ // delay - (Number) A zero-or-greater delay in milliseconds. For event
+ // callbacks, values around 100 or 250 (or even higher) are most useful.
+ // no_trailing - (Boolean) Optional, defaults to false. If no_trailing is
+ // true, callback will only execute every `delay` milliseconds while the
+ // throttled-function is being called. If no_trailing is false or
+ // unspecified, callback will be executed one final time after the last
+ // throttled-function call. (After the throttled-function has not been
+ // called for `delay` milliseconds, the internal counter is reset)
+ // callback - (Function) A function to be executed after delay milliseconds.
+ // The `this` context and all arguments are passed through, as-is, to
+ // `callback` when the throttled-function is executed.
+ //
+ // Returns:
+ //
+ // (Function) A new, throttled, function.
+
+ $.throttle = jq_throttle = function( delay, no_trailing, callback, debounce_mode ) {
+ // After wrapper has stopped being called, this timeout ensures that
+ // `callback` is executed at the proper times in `throttle` and `end`
+ // debounce modes.
+ var timeout_id,
+
+ // Keep track of the last time `callback` was executed.
+ last_exec = 0;
+
+ // `no_trailing` defaults to falsy.
+ if ( typeof no_trailing !== 'boolean' ) {
+ debounce_mode = callback;
+ callback = no_trailing;
+ no_trailing = undefined;
+ }
+
+ // The `wrapper` function encapsulates all of the throttling / debouncing
+ // functionality and when executed will limit the rate at which `callback`
+ // is executed.
+ function wrapper() {
+ var that = this,
+ elapsed = +new Date() - last_exec,
+ args = arguments;
+
+ // Execute `callback` and update the `last_exec` timestamp.
+ function exec() {
+ last_exec = +new Date();
+ callback.apply( that, args );
+ };
+
+ // If `debounce_mode` is true (at_begin) this is used to clear the flag
+ // to allow future `callback` executions.
+ function clear() {
+ timeout_id = undefined;
+ };
+
+ if ( debounce_mode && !timeout_id ) {
+ // Since `wrapper` is being called for the first time and
+ // `debounce_mode` is true (at_begin), execute `callback`.
+ exec();
+ }
+
+ // Clear any existing timeout.
+ timeout_id && clearTimeout( timeout_id );
+
+ if ( debounce_mode === undefined && elapsed > delay ) {
+ // In throttle mode, if `delay` time has been exceeded, execute
+ // `callback`.
+ exec();
+
+ } else if ( no_trailing !== true ) {
+ // In trailing throttle mode, since `delay` time has not been
+ // exceeded, schedule `callback` to execute `delay` ms after most
+ // recent execution.
+ //
+ // If `debounce_mode` is true (at_begin), schedule `clear` to execute
+ // after `delay` ms.
+ //
+ // If `debounce_mode` is false (at end), schedule `callback` to
+ // execute after `delay` ms.
+ timeout_id = setTimeout( debounce_mode ? clear : exec, debounce_mode === undefined ? delay - elapsed : delay );
+ }
+ };
+
+ // Set the guid of `wrapper` function to the same of original callback, so
+ // it can be removed in jQuery 1.4+ .unbind or .die by using the original
+ // callback as a reference.
+ if ( $.guid ) {
+ wrapper.guid = callback.guid = callback.guid || $.guid++;
+ }
+
+ // Return the wrapper function.
+ return wrapper;
+ };
+
+ // Method: jQuery.debounce
+ //
+ // Debounce execution of a function. Debouncing, unlike throttling,
+ // guarantees that a function is only executed a single time, either at the
+ // very beginning of a series of calls, or at the very end. If you want to
+ // simply rate-limit execution of a function, see the <jQuery.throttle>
+ // method.
+ //
+ // In this visualization, | is a debounced-function call and X is the actual
+ // callback execution:
+ //
+ // > Debounced with `at_begin` specified as false or unspecified:
+ // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
+ // > X X
+ // >
+ // > Debounced with `at_begin` specified as true:
+ // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
+ // > X X
+ //
+ // Usage:
+ //
+ // > var debounced = jQuery.debounce( delay, [ at_begin, ] callback );
+ // >
+ // > jQuery('selector').bind( 'someevent', debounced );
+ // > jQuery('selector').unbind( 'someevent', debounced );
+ //
+ // This also works in jQuery 1.4+:
+ //
+ // > jQuery('selector').bind( 'someevent', jQuery.debounce( delay, [ at_begin, ] callback ) );
+ // > jQuery('selector').unbind( 'someevent', callback );
+ //
+ // Arguments:
+ //
+ // delay - (Number) A zero-or-greater delay in milliseconds. For event
+ // callbacks, values around 100 or 250 (or even higher) are most useful.
+ // at_begin - (Boolean) Optional, defaults to false. If at_begin is false or
+ // unspecified, callback will only be executed `delay` milliseconds after
+ // the last debounced-function call. If at_begin is true, callback will be
+ // executed only at the first debounced-function call. (After the
+ // throttled-function has not been called for `delay` milliseconds, the
+ // internal counter is reset)
+ // callback - (Function) A function to be executed after delay milliseconds.
+ // The `this` context and all arguments are passed through, as-is, to
+ // `callback` when the debounced-function is executed.
+ //
+ // Returns:
+ //
+ // (Function) A new, debounced, function.
+
+ $.debounce = function( delay, at_begin, callback ) {
+ return callback === undefined
+ ? jq_throttle( delay, at_begin, false )
+ : jq_throttle( delay, callback, at_begin !== false );
+ };
+
+})(this);
--- /dev/null
+/*!
+ * jQuery Cookie Plugin v1.3.1
+ * https://github.com/carhartl/jquery-cookie
+ *
+ * Copyright 2013 Klaus Hartl
+ * Released under the MIT license
+ */
+(function ($, document, undefined) {
+
+ var pluses = /\+/g;
+
+ function raw(s) {
+ return s;
+ }
+
+ function decoded(s) {
+ return unRfc2068(decodeURIComponent(s.replace(pluses, ' ')));
+ }
+
+ function unRfc2068(value) {
+ if (value.indexOf('"') === 0) {
+ // This is a quoted cookie as according to RFC2068, unescape
+ value = value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
+ }
+ return value;
+ }
+
+ function fromJSON(value) {
+ return config.json ? JSON.parse(value) : value;
+ }
+
+ var config = $.cookie = function (key, value, options) {
+
+ // write
+ if (value !== undefined) {
+ options = $.extend({}, config.defaults, options);
+
+ if (value === null) {
+ options.expires = -1;
+ }
+
+ if (typeof options.expires === 'number') {
+ var days = options.expires, t = options.expires = new Date();
+ t.setDate(t.getDate() + days);
+ }
+
+ value = config.json ? JSON.stringify(value) : String(value);
+
+ return (document.cookie = [
+ encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value),
+ options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
+ options.path ? '; path=' + options.path : '',
+ options.domain ? '; domain=' + options.domain : '',
+ options.secure ? '; secure' : ''
+ ].join(''));
+ }
+
+ // read
+ var decode = config.raw ? raw : decoded;
+ var cookies = document.cookie.split('; ');
+ var result = key ? null : {};
+ for (var i = 0, l = cookies.length; i < l; i++) {
+ var parts = cookies[i].split('=');
+ var name = decode(parts.shift());
+ var cookie = decode(parts.join('='));
+
+ if (key && key === name) {
+ result = fromJSON(cookie);
+ break;
+ }
+
+ if (!key) {
+ result[name] = fromJSON(cookie);
+ }
+ }
+
+ return result;
+ };
+
+ config.defaults = {};
+
+ $.removeCookie = function (key, options) {
+ if ($.cookie(key) !== null) {
+ $.cookie(key, null, options);
+ return true;
+ }
+ return false;
+ };
+
+})(jQuery, document);
--- /dev/null
+/*!
+ * jQuery Form Plugin
+ * version: 3.14 (30-JUL-2012)
+ * @requires jQuery v1.3.2 or later
+ *
+ * Examples and documentation at: http://malsup.com/jquery/form/
+ * Project repository: https://github.com/malsup/form
+ * Dual licensed under the MIT and GPL licenses:
+ * http://malsup.github.com/mit-license.txt
+ * http://malsup.github.com/gpl-license-v2.txt
+ */
+/*global ActiveXObject alert */
+;(function($) {
+"use strict";
+
+/*
+ Usage Note:
+ -----------
+ Do not use both ajaxSubmit and ajaxForm on the same form. These
+ functions are mutually exclusive. Use ajaxSubmit if you want
+ to bind your own submit handler to the form. For example,
+
+ $(document).ready(function() {
+ $('#myForm').on('submit', function(e) {
+ e.preventDefault(); // <-- important
+ $(this).ajaxSubmit({
+ target: '#output'
+ });
+ });
+ });
+
+ Use ajaxForm when you want the plugin to manage all the event binding
+ for you. For example,
+
+ $(document).ready(function() {
+ $('#myForm').ajaxForm({
+ target: '#output'
+ });
+ });
+
+ You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
+ form does not have to exist when you invoke ajaxForm:
+
+ $('#myForm').ajaxForm({
+ delegation: true,
+ target: '#output'
+ });
+
+ When using ajaxForm, the ajaxSubmit function will be invoked for you
+ at the appropriate time.
+*/
+
+/**
+ * Feature detection
+ */
+var feature = {};
+feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
+feature.formdata = window.FormData !== undefined;
+
+/**
+ * ajaxSubmit() provides a mechanism for immediately submitting
+ * an HTML form using AJAX.
+ */
+$.fn.ajaxSubmit = function(options) {
+ /*jshint scripturl:true */
+
+ // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
+ if (!this.length) {
+ log('ajaxSubmit: skipping submit process - no element selected');
+ return this;
+ }
+
+ var method, action, url, $form = this;
+
+ if (typeof options == 'function') {
+ options = { success: options };
+ }
+
+ method = this.attr('method');
+ action = this.attr('action');
+ url = (typeof action === 'string') ? $.trim(action) : '';
+ url = url || window.location.href || '';
+ if (url) {
+ // clean url (don't include hash vaue)
+ url = (url.match(/^([^#]+)/)||[])[1];
+ }
+
+ options = $.extend(true, {
+ url: url,
+ success: $.ajaxSettings.success,
+ type: method || 'GET',
+ iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
+ }, options);
+
+ // hook for manipulating the form data before it is extracted;
+ // convenient for use with rich editors like tinyMCE or FCKEditor
+ var veto = {};
+ this.trigger('form-pre-serialize', [this, options, veto]);
+ if (veto.veto) {
+ log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
+ return this;
+ }
+
+ // provide opportunity to alter form data before it is serialized
+ if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
+ log('ajaxSubmit: submit aborted via beforeSerialize callback');
+ return this;
+ }
+
+ var traditional = options.traditional;
+ if ( traditional === undefined ) {
+ traditional = $.ajaxSettings.traditional;
+ }
+
+ var elements = [];
+ var qx, a = this.formToArray(options.semantic, elements);
+ if (options.data) {
+ options.extraData = options.data;
+ qx = $.param(options.data, traditional);
+ }
+
+ // give pre-submit callback an opportunity to abort the submit
+ if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
+ log('ajaxSubmit: submit aborted via beforeSubmit callback');
+ return this;
+ }
+
+ // fire vetoable 'validate' event
+ this.trigger('form-submit-validate', [a, this, options, veto]);
+ if (veto.veto) {
+ log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
+ return this;
+ }
+
+ var q = $.param(a, traditional);
+ if (qx) {
+ q = ( q ? (q + '&' + qx) : qx );
+ }
+ if (options.type.toUpperCase() == 'GET') {
+ options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
+ options.data = null; // data is null for 'get'
+ }
+ else {
+ options.data = q; // data is the query string for 'post'
+ }
+
+ var callbacks = [];
+ if (options.resetForm) {
+ callbacks.push(function() { $form.resetForm(); });
+ }
+ if (options.clearForm) {
+ callbacks.push(function() { $form.clearForm(options.includeHidden); });
+ }
+
+ // perform a load on the target only if dataType is not provided
+ if (!options.dataType && options.target) {
+ var oldSuccess = options.success || function(){};
+ callbacks.push(function(data) {
+ var fn = options.replaceTarget ? 'replaceWith' : 'html';
+ $(options.target)[fn](data).each(oldSuccess, arguments);
+ });
+ }
+ else if (options.success) {
+ callbacks.push(options.success);
+ }
+
+ options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
+ var context = options.context || this ; // jQuery 1.4+ supports scope context
+ for (var i=0, max=callbacks.length; i < max; i++) {
+ callbacks[i].apply(context, [data, status, xhr || $form, $form]);
+ }
+ };
+
+ // are there files to upload?
+ var fileInputs = $('input:file:enabled[value]', this); // [value] (issue #113)
+ var hasFileInputs = fileInputs.length > 0;
+ var mp = 'multipart/form-data';
+ var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
+
+ var fileAPI = feature.fileapi && feature.formdata;
+ log("fileAPI :" + fileAPI);
+ var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
+
+ // options.iframe allows user to force iframe mode
+ // 06-NOV-09: now defaulting to iframe mode if file input is detected
+ if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
+ // hack to fix Safari hang (thanks to Tim Molendijk for this)
+ // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
+ if (options.closeKeepAlive) {
+ $.get(options.closeKeepAlive, function() {
+ fileUploadIframe(a);
+ });
+ }
+ else {
+ fileUploadIframe(a);
+ }
+ }
+ else if ((hasFileInputs || multipart) && fileAPI) {
+ fileUploadXhr(a);
+ }
+ else {
+ $.ajax(options);
+ }
+
+ // clear element array
+ for (var k=0; k < elements.length; k++)
+ elements[k] = null;
+
+ // fire 'notify' event
+ this.trigger('form-submit-notify', [this, options]);
+ return this;
+
+ // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
+ function fileUploadXhr(a) {
+ var formdata = new FormData();
+
+ for (var i=0; i < a.length; i++) {
+ formdata.append(a[i].name, a[i].value);
+ }
+
+ if (options.extraData) {
+ for (var p in options.extraData)
+ if (options.extraData.hasOwnProperty(p))
+ formdata.append(p, options.extraData[p]);
+ }
+
+ options.data = null;
+
+ var s = $.extend(true, {}, $.ajaxSettings, options, {
+ contentType: false,
+ processData: false,
+ cache: false,
+ type: 'POST'
+ });
+
+ if (options.uploadProgress) {
+ // workaround because jqXHR does not expose upload property
+ s.xhr = function() {
+ var xhr = jQuery.ajaxSettings.xhr();
+ if (xhr.upload) {
+ xhr.upload.onprogress = function(event) {
+ var percent = 0;
+ var position = event.loaded || event.position; /*event.position is deprecated*/
+ var total = event.total;
+ if (event.lengthComputable) {
+ percent = Math.ceil(position / total * 100);
+ }
+ options.uploadProgress(event, position, total, percent);
+ };
+ }
+ return xhr;
+ };
+ }
+
+ s.data = null;
+ var beforeSend = s.beforeSend;
+ s.beforeSend = function(xhr, o) {
+ o.data = formdata;
+ if(beforeSend)
+ beforeSend.call(this, xhr, o);
+ };
+ $.ajax(s);
+ }
+
+ // private function for handling file uploads (hat tip to YAHOO!)
+ function fileUploadIframe(a) {
+ var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
+ var useProp = !!$.fn.prop;
+
+ if ($(':input[name=submit],:input[id=submit]', form).length) {
+ // if there is an input with a name or id of 'submit' then we won't be
+ // able to invoke the submit fn on the form (at least not x-browser)
+ alert('Error: Form elements must not have name or id of "submit".');
+ return;
+ }
+
+ if (a) {
+ // ensure that every serialized input is still enabled
+ for (i=0; i < elements.length; i++) {
+ el = $(elements[i]);
+ if ( useProp )
+ el.prop('disabled', false);
+ else
+ el.removeAttr('disabled');
+ }
+ }
+
+ s = $.extend(true, {}, $.ajaxSettings, options);
+ s.context = s.context || s;
+ id = 'jqFormIO' + (new Date().getTime());
+ if (s.iframeTarget) {
+ $io = $(s.iframeTarget);
+ n = $io.attr('name');
+ if (!n)
+ $io.attr('name', id);
+ else
+ id = n;
+ }
+ else {
+ $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
+ $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
+ }
+ io = $io[0];
+
+
+ xhr = { // mock object
+ aborted: 0,
+ responseText: null,
+ responseXML: null,
+ status: 0,
+ statusText: 'n/a',
+ getAllResponseHeaders: function() {},
+ getResponseHeader: function() {},
+ setRequestHeader: function() {},
+ abort: function(status) {
+ var e = (status === 'timeout' ? 'timeout' : 'aborted');
+ log('aborting upload... ' + e);
+ this.aborted = 1;
+ // #214
+ if (io.contentWindow.document.execCommand) {
+ try { // #214
+ io.contentWindow.document.execCommand('Stop');
+ } catch(ignore) {}
+ }
+ $io.attr('src', s.iframeSrc); // abort op in progress
+ xhr.error = e;
+ if (s.error)
+ s.error.call(s.context, xhr, e, status);
+ if (g)
+ $.event.trigger("ajaxError", [xhr, s, e]);
+ if (s.complete)
+ s.complete.call(s.context, xhr, e);
+ }
+ };
+
+ g = s.global;
+ // trigger ajax global events so that activity/block indicators work like normal
+ if (g && 0 === $.active++) {
+ $.event.trigger("ajaxStart");
+ }
+ if (g) {
+ $.event.trigger("ajaxSend", [xhr, s]);
+ }
+
+ if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
+ if (s.global) {
+ $.active--;
+ }
+ return;
+ }
+ if (xhr.aborted) {
+ return;
+ }
+
+ // add submitting element to data if we know it
+ sub = form.clk;
+ if (sub) {
+ n = sub.name;
+ if (n && !sub.disabled) {
+ s.extraData = s.extraData || {};
+ s.extraData[n] = sub.value;
+ if (sub.type == "image") {
+ s.extraData[n+'.x'] = form.clk_x;
+ s.extraData[n+'.y'] = form.clk_y;
+ }
+ }
+ }
+
+ var CLIENT_TIMEOUT_ABORT = 1;
+ var SERVER_ABORT = 2;
+
+ function getDoc(frame) {
+ var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
+ return doc;
+ }
+
+ // Rails CSRF hack (thanks to Yvan Barthelemy)
+ var csrf_token = $('meta[name=csrf-token]').attr('content');
+ var csrf_param = $('meta[name=csrf-param]').attr('content');
+ if (csrf_param && csrf_token) {
+ s.extraData = s.extraData || {};
+ s.extraData[csrf_param] = csrf_token;
+ }
+
+ // take a breath so that pending repaints get some cpu time before the upload starts
+ function doSubmit() {
+ // make sure form attrs are set
+ var t = $form.attr('target'), a = $form.attr('action');
+
+ // update form attrs in IE friendly way
+ form.setAttribute('target',id);
+ if (!method) {
+ form.setAttribute('method', 'POST');
+ }
+ if (a != s.url) {
+ form.setAttribute('action', s.url);
+ }
+
+ // ie borks in some cases when setting encoding
+ if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
+ $form.attr({
+ encoding: 'multipart/form-data',
+ enctype: 'multipart/form-data'
+ });
+ }
+
+ // support timout
+ if (s.timeout) {
+ timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
+ }
+
+ // look for server aborts
+ function checkState() {
+ try {
+ var state = getDoc(io).readyState;
+ log('state = ' + state);
+ if (state && state.toLowerCase() == 'uninitialized')
+ setTimeout(checkState,50);
+ }
+ catch(e) {
+ log('Server abort: ' , e, ' (', e.name, ')');
+ cb(SERVER_ABORT);
+ if (timeoutHandle)
+ clearTimeout(timeoutHandle);
+ timeoutHandle = undefined;
+ }
+ }
+
+ // add "extra" data to form if provided in options
+ var extraInputs = [];
+ try {
+ if (s.extraData) {
+ for (var n in s.extraData) {
+ if (s.extraData.hasOwnProperty(n)) {
+ // if using the $.param format that allows for multiple values with the same name
+ if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
+ extraInputs.push(
+ $('<input type="hidden" name="'+s.extraData[n].name+'">').attr('value',s.extraData[n].value)
+ .appendTo(form)[0]);
+ } else {
+ extraInputs.push(
+ $('<input type="hidden" name="'+n+'">').attr('value',s.extraData[n])
+ .appendTo(form)[0]);
+ }
+ }
+ }
+ }
+
+ if (!s.iframeTarget) {
+ // add iframe to doc and submit the form
+ $io.appendTo('body');
+ if (io.attachEvent)
+ io.attachEvent('onload', cb);
+ else
+ io.addEventListener('load', cb, false);
+ }
+ setTimeout(checkState,15);
+ form.submit();
+ }
+ finally {
+ // reset attrs and remove "extra" input elements
+ form.setAttribute('action',a);
+ if(t) {
+ form.setAttribute('target', t);
+ } else {
+ $form.removeAttr('target');
+ }
+ $(extraInputs).remove();
+ }
+ }
+
+ if (s.forceSync) {
+ doSubmit();
+ }
+ else {
+ setTimeout(doSubmit, 10); // this lets dom updates render
+ }
+
+ var data, doc, domCheckCount = 50, callbackProcessed;
+
+ function cb(e) {
+ if (xhr.aborted || callbackProcessed) {
+ return;
+ }
+ try {
+ doc = getDoc(io);
+ }
+ catch(ex) {
+ log('cannot access response document: ', ex);
+ e = SERVER_ABORT;
+ }
+ if (e === CLIENT_TIMEOUT_ABORT && xhr) {
+ xhr.abort('timeout');
+ return;
+ }
+ else if (e == SERVER_ABORT && xhr) {
+ xhr.abort('server abort');
+ return;
+ }
+
+ if (!doc || doc.location.href == s.iframeSrc) {
+ // response not received yet
+ if (!timedOut)
+ return;
+ }
+ if (io.detachEvent)
+ io.detachEvent('onload', cb);
+ else
+ io.removeEventListener('load', cb, false);
+
+ var status = 'success', errMsg;
+ try {
+ if (timedOut) {
+ throw 'timeout';
+ }
+
+ var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
+ log('isXml='+isXml);
+ if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
+ if (--domCheckCount) {
+ // in some browsers (Opera) the iframe DOM is not always traversable when
+ // the onload callback fires, so we loop a bit to accommodate
+ log('requeing onLoad callback, DOM not available');
+ setTimeout(cb, 250);
+ return;
+ }
+ // let this fall through because server response could be an empty document
+ //log('Could not access iframe DOM after mutiple tries.');
+ //throw 'DOMException: not available';
+ }
+
+ //log('response detected');
+ var docRoot = doc.body ? doc.body : doc.documentElement;
+ xhr.responseText = docRoot ? docRoot.innerHTML : null;
+ xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
+ if (isXml)
+ s.dataType = 'xml';
+ xhr.getResponseHeader = function(header){
+ var headers = {'content-type': s.dataType};
+ return headers[header];
+ };
+ // support for XHR 'status' & 'statusText' emulation :
+ if (docRoot) {
+ xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
+ xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
+ }
+
+ var dt = (s.dataType || '').toLowerCase();
+ var scr = /(json|script|text)/.test(dt);
+ if (scr || s.textarea) {
+ // see if user embedded response in textarea
+ var ta = doc.getElementsByTagName('textarea')[0];
+ if (ta) {
+ xhr.responseText = ta.value;
+ // support for XHR 'status' & 'statusText' emulation :
+ xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
+ xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
+ }
+ else if (scr) {
+ // account for browsers injecting pre around json response
+ var pre = doc.getElementsByTagName('pre')[0];
+ var b = doc.getElementsByTagName('body')[0];
+ if (pre) {
+ xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
+ }
+ else if (b) {
+ xhr.responseText = b.textContent ? b.textContent : b.innerText;
+ }
+ }
+ }
+ else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
+ xhr.responseXML = toXml(xhr.responseText);
+ }
+
+ try {
+ data = httpData(xhr, dt, s);
+ }
+ catch (e) {
+ status = 'parsererror';
+ xhr.error = errMsg = (e || status);
+ }
+ }
+ catch (e) {
+ log('error caught: ',e);
+ status = 'error';
+ xhr.error = errMsg = (e || status);
+ }
+
+ if (xhr.aborted) {
+ log('upload aborted');
+ status = null;
+ }
+
+ if (xhr.status) { // we've set xhr.status
+ status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
+ }
+
+ // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
+ if (status === 'success') {
+ if (s.success)
+ s.success.call(s.context, data, 'success', xhr);
+ if (g)
+ $.event.trigger("ajaxSuccess", [xhr, s]);
+ }
+ else if (status) {
+ if (errMsg === undefined)
+ errMsg = xhr.statusText;
+ if (s.error)
+ s.error.call(s.context, xhr, status, errMsg);
+ if (g)
+ $.event.trigger("ajaxError", [xhr, s, errMsg]);
+ }
+
+ if (g)
+ $.event.trigger("ajaxComplete", [xhr, s]);
+
+ if (g && ! --$.active) {
+ $.event.trigger("ajaxStop");
+ }
+
+ if (s.complete)
+ s.complete.call(s.context, xhr, status);
+
+ callbackProcessed = true;
+ if (s.timeout)
+ clearTimeout(timeoutHandle);
+
+ // clean up
+ setTimeout(function() {
+ if (!s.iframeTarget)
+ $io.remove();
+ xhr.responseXML = null;
+ }, 100);
+ }
+
+ var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
+ if (window.ActiveXObject) {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ doc.loadXML(s);
+ }
+ else {
+ doc = (new DOMParser()).parseFromString(s, 'text/xml');
+ }
+ return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
+ };
+ var parseJSON = $.parseJSON || function(s) {
+ /*jslint evil:true */
+ return window['eval']('(' + s + ')');
+ };
+
+ var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
+
+ var ct = xhr.getResponseHeader('content-type') || '',
+ xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
+ data = xml ? xhr.responseXML : xhr.responseText;
+
+ if (xml && data.documentElement.nodeName === 'parsererror') {
+ if ($.error)
+ $.error('parsererror');
+ }
+ if (s && s.dataFilter) {
+ data = s.dataFilter(data, type);
+ }
+ if (typeof data === 'string') {
+ if (type === 'json' || !type && ct.indexOf('json') >= 0) {
+ data = parseJSON(data);
+ } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
+ $.globalEval(data);
+ }
+ }
+ return data;
+ };
+ }
+};
+
+/**
+ * ajaxForm() provides a mechanism for fully automating form submission.
+ *
+ * The advantages of using this method instead of ajaxSubmit() are:
+ *
+ * 1: This method will include coordinates for <input type="image" /> elements (if the element
+ * is used to submit the form).
+ * 2. This method will include the submit element's name/value data (for the element that was
+ * used to submit the form).
+ * 3. This method binds the submit() method to the form for you.
+ *
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
+ * passes the options argument along after properly binding events for submit elements and
+ * the form itself.
+ */
+$.fn.ajaxForm = function(options) {
+ options = options || {};
+ options.delegation = options.delegation && $.isFunction($.fn.on);
+
+ // in jQuery 1.3+ we can fix mistakes with the ready state
+ if (!options.delegation && this.length === 0) {
+ var o = { s: this.selector, c: this.context };
+ if (!$.isReady && o.s) {
+ log('DOM not ready, queuing ajaxForm');
+ $(function() {
+ $(o.s,o.c).ajaxForm(options);
+ });
+ return this;
+ }
+ // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
+ log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
+ return this;
+ }
+
+ if ( options.delegation ) {
+ $(document)
+ .off('submit.form-plugin', this.selector, doAjaxSubmit)
+ .off('click.form-plugin', this.selector, captureSubmittingElement)
+ .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
+ .on('click.form-plugin', this.selector, options, captureSubmittingElement);
+ return this;
+ }
+
+ return this.ajaxFormUnbind()
+ .bind('submit.form-plugin', options, doAjaxSubmit)
+ .bind('click.form-plugin', options, captureSubmittingElement);
+};
+
+// private event handlers
+function doAjaxSubmit(e) {
+ /*jshint validthis:true */
+ var options = e.data;
+ if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
+ e.preventDefault();
+ $(this).ajaxSubmit(options);
+ }
+}
+
+function captureSubmittingElement(e) {
+ /*jshint validthis:true */
+ var target = e.target;
+ var $el = $(target);
+ if (!($el.is(":submit,input:image"))) {
+ // is this a child element of the submit el? (ex: a span within a button)
+ var t = $el.closest(':submit');
+ if (t.length === 0) {
+ return;
+ }
+ target = t[0];
+ }
+ var form = this;
+ form.clk = target;
+ if (target.type == 'image') {
+ if (e.offsetX !== undefined) {
+ form.clk_x = e.offsetX;
+ form.clk_y = e.offsetY;
+ } else if (typeof $.fn.offset == 'function') {
+ var offset = $el.offset();
+ form.clk_x = e.pageX - offset.left;
+ form.clk_y = e.pageY - offset.top;
+ } else {
+ form.clk_x = e.pageX - target.offsetLeft;
+ form.clk_y = e.pageY - target.offsetTop;
+ }
+ }
+ // clear form vars
+ setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
+}
+
+
+// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
+$.fn.ajaxFormUnbind = function() {
+ return this.unbind('submit.form-plugin click.form-plugin');
+};
+
+/**
+ * formToArray() gathers form element data into an array of objects that can
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
+ * Each object in the array has both a 'name' and 'value' property. An example of
+ * an array for a simple login form might be:
+ *
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * It is this array that is passed to pre-submit callback functions provided to the
+ * ajaxSubmit() and ajaxForm() methods.
+ */
+$.fn.formToArray = function(semantic, elements) {
+ var a = [];
+ if (this.length === 0) {
+ return a;
+ }
+
+ var form = this[0];
+ var els = semantic ? form.getElementsByTagName('*') : form.elements;
+ if (!els) {
+ return a;
+ }
+
+ var i,j,n,v,el,max,jmax;
+ for(i=0, max=els.length; i < max; i++) {
+ el = els[i];
+ n = el.name;
+ if (!n) {
+ continue;
+ }
+
+ if (semantic && form.clk && el.type == "image") {
+ // handle image inputs on the fly when semantic == true
+ if(!el.disabled && form.clk == el) {
+ a.push({name: n, value: $(el).val(), type: el.type });
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ }
+ continue;
+ }
+
+ v = $.fieldValue(el, true);
+ if (v && v.constructor == Array) {
+ if (elements)
+ elements.push(el);
+ for(j=0, jmax=v.length; j < jmax; j++) {
+ a.push({name: n, value: v[j]});
+ }
+ }
+ else if (feature.fileapi && el.type == 'file' && !el.disabled) {
+ if (elements)
+ elements.push(el);
+ var files = el.files;
+ if (files.length) {
+ for (j=0; j < files.length; j++) {
+ a.push({name: n, value: files[j], type: el.type});
+ }
+ }
+ else {
+ // #180
+ a.push({ name: n, value: '', type: el.type });
+ }
+ }
+ else if (v !== null && typeof v != 'undefined') {
+ if (elements)
+ elements.push(el);
+ a.push({name: n, value: v, type: el.type, required: el.required});
+ }
+ }
+
+ if (!semantic && form.clk) {
+ // input type=='image' are not found in elements array! handle it here
+ var $input = $(form.clk), input = $input[0];
+ n = input.name;
+ if (n && !input.disabled && input.type == 'image') {
+ a.push({name: n, value: $input.val()});
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ }
+ }
+ return a;
+};
+
+/**
+ * Serializes form data into a 'submittable' string. This method will return a string
+ * in the format: name1=value1&name2=value2
+ */
+$.fn.formSerialize = function(semantic) {
+ //hand off to jQuery.param for proper encoding
+ return $.param(this.formToArray(semantic));
+};
+
+/**
+ * Serializes all field elements in the jQuery object into a query string.
+ * This method will return a string in the format: name1=value1&name2=value2
+ */
+$.fn.fieldSerialize = function(successful) {
+ var a = [];
+ this.each(function() {
+ var n = this.name;
+ if (!n) {
+ return;
+ }
+ var v = $.fieldValue(this, successful);
+ if (v && v.constructor == Array) {
+ for (var i=0,max=v.length; i < max; i++) {
+ a.push({name: n, value: v[i]});
+ }
+ }
+ else if (v !== null && typeof v != 'undefined') {
+ a.push({name: this.name, value: v});
+ }
+ });
+ //hand off to jQuery.param for proper encoding
+ return $.param(a);
+};
+
+/**
+ * Returns the value(s) of the element in the matched set. For example, consider the following form:
+ *
+ * <form><fieldset>
+ * <input name="A" type="text" />
+ * <input name="A" type="text" />
+ * <input name="B" type="checkbox" value="B1" />
+ * <input name="B" type="checkbox" value="B2"/>
+ * <input name="C" type="radio" value="C1" />
+ * <input name="C" type="radio" value="C2" />
+ * </fieldset></form>
+ *
+ * var v = $(':text').fieldValue();
+ * // if no values are entered into the text inputs
+ * v == ['','']
+ * // if values entered into the text inputs are 'foo' and 'bar'
+ * v == ['foo','bar']
+ *
+ * var v = $(':checkbox').fieldValue();
+ * // if neither checkbox is checked
+ * v === undefined
+ * // if both checkboxes are checked
+ * v == ['B1', 'B2']
+ *
+ * var v = $(':radio').fieldValue();
+ * // if neither radio is checked
+ * v === undefined
+ * // if first radio is checked
+ * v == ['C1']
+ *
+ * The successful argument controls whether or not the field element must be 'successful'
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true. If this value is false the value(s)
+ * for each element is returned.
+ *
+ * Note: This method *always* returns an array. If no valid value can be determined the
+ * array will be empty, otherwise it will contain one or more values.
+ */
+$.fn.fieldValue = function(successful) {
+ for (var val=[], i=0, max=this.length; i < max; i++) {
+ var el = this[i];
+ var v = $.fieldValue(el, successful);
+ if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
+ continue;
+ }
+ if (v.constructor == Array)
+ $.merge(val, v);
+ else
+ val.push(v);
+ }
+ return val;
+};
+
+/**
+ * Returns the value of the field element.
+ */
+$.fieldValue = function(el, successful) {
+ var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
+ if (successful === undefined) {
+ successful = true;
+ }
+
+ if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
+ (t == 'checkbox' || t == 'radio') && !el.checked ||
+ (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
+ tag == 'select' && el.selectedIndex == -1)) {
+ return null;
+ }
+
+ if (tag == 'select') {
+ var index = el.selectedIndex;
+ if (index < 0) {
+ return null;
+ }
+ var a = [], ops = el.options;
+ var one = (t == 'select-one');
+ var max = (one ? index+1 : ops.length);
+ for(var i=(one ? index : 0); i < max; i++) {
+ var op = ops[i];
+ if (op.selected) {
+ var v = op.value;
+ if (!v) { // extra pain for IE...
+ v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
+ }
+ if (one) {
+ return v;
+ }
+ a.push(v);
+ }
+ }
+ return a;
+ }
+ return $(el).val();
+};
+
+/**
+ * Clears the form data. Takes the following actions on the form's input fields:
+ * - input text fields will have their 'value' property set to the empty string
+ * - select elements will have their 'selectedIndex' property set to -1
+ * - checkbox and radio inputs will have their 'checked' property set to false
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
+ * - button elements will *not* be effected
+ */
+$.fn.clearForm = function(includeHidden) {
+ return this.each(function() {
+ $('input,select,textarea', this).clearFields(includeHidden);
+ });
+};
+
+/**
+ * Clears the selected form elements.
+ */
+$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
+ var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
+ return this.each(function() {
+ var t = this.type, tag = this.tagName.toLowerCase();
+ if (re.test(t) || tag == 'textarea') {
+ this.value = '';
+ }
+ else if (t == 'checkbox' || t == 'radio') {
+ this.checked = false;
+ }
+ else if (tag == 'select') {
+ this.selectedIndex = -1;
+ }
+ else if (includeHidden) {
+ // includeHidden can be the value true, or it can be a selector string
+ // indicating a special test; for example:
+ // $('#myForm').clearForm('.special:hidden')
+ // the above would clean hidden inputs that have the class of 'special'
+ if ( (includeHidden === true && /hidden/.test(t)) ||
+ (typeof includeHidden == 'string' && $(this).is(includeHidden)) )
+ this.value = '';
+ }
+ });
+};
+
+/**
+ * Resets the form data. Causes all form elements to be reset to their original value.
+ */
+$.fn.resetForm = function() {
+ return this.each(function() {
+ // guard against an input with the name of 'reset'
+ // note that IE reports the reset function as an 'object'
+ if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
+ this.reset();
+ }
+ });
+};
+
+/**
+ * Enables or disables any matching elements.
+ */
+$.fn.enable = function(b) {
+ if (b === undefined) {
+ b = true;
+ }
+ return this.each(function() {
+ this.disabled = !b;
+ });
+};
+
+/**
+ * Checks/unchecks any matching checkboxes or radio buttons and
+ * selects/deselects and matching option elements.
+ */
+$.fn.selected = function(select) {
+ if (select === undefined) {
+ select = true;
+ }
+ return this.each(function() {
+ var t = this.type;
+ if (t == 'checkbox' || t == 'radio') {
+ this.checked = select;
+ }
+ else if (this.tagName.toLowerCase() == 'option') {
+ var $sel = $(this).parent('select');
+ if (select && $sel[0] && $sel[0].type == 'select-one') {
+ // deselect all other options
+ $sel.find('option').selected(false);
+ }
+ this.selected = select;
+ }
+ });
+};
+
+// expose debug var
+$.fn.ajaxSubmit.debug = false;
+
+// helper fn for console logging
+function log() {
+ if (!$.fn.ajaxSubmit.debug)
+ return;
+ var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
+ if (window.console && window.console.log) {
+ window.console.log(msg);
+ }
+ else if (window.opera && window.opera.postError) {
+ window.opera.postError(msg);
+ }
+}
+
+})(jQuery);
--- /dev/null
+/**
+ * jQuery fullscreen plugin v2.0.0-git (9f8f97d127)
+ * https://github.com/theopolisme/jquery-fullscreen
+ *
+ * Copyright (c) 2013 Theopolisme <theopolismewiki@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+( function ( $ ) {
+ var setupFullscreen,
+ fsClass = 'jq-fullscreened';
+
+ /**
+ * On fullscreenchange, trigger a jq-fullscreen-change event
+ * The event is given an object, which contains the fullscreened DOM element (element), if any
+ * and a boolean value (fullscreen) indicating if we've entered or exited fullscreen mode
+ * Also remove the 'fullscreened' class from elements that are no longer fullscreen
+ */
+ function handleFullscreenChange () {
+ var fullscreenElement = document.fullscreenElement ||
+ document.mozFullScreenElement ||
+ document.webkitFullscreenElement ||
+ document.msFullscreenElement;
+
+ if ( !fullscreenElement ) {
+ $( '.' + fsClass ).data( 'isFullscreened', false ).removeClass( fsClass );
+ }
+
+ $( document ).trigger( $.Event( 'jq-fullscreen-change', { element: fullscreenElement, fullscreen: !!fullscreenElement } ) );
+ }
+
+ /**
+ * Enters full screen with the "this" element in focus.
+ * Check the .data( 'isFullscreened' ) of the return value to check
+ * success or failure, if you're into that sort of thing.
+ * @chainable
+ * @return {jQuery}
+ */
+ function enterFullscreen () {
+ var element = this.get(0),
+ $element = this.first();
+ if ( element ) {
+ if ( element.requestFullscreen ) {
+ element.requestFullscreen();
+ } else if ( element.mozRequestFullScreen ) {
+ element.mozRequestFullScreen();
+ } else if ( element.webkitRequestFullscreen ) {
+ element.webkitRequestFullscreen();
+ } else if ( element.msRequestFullscreen ) {
+ element.msRequestFullscreen();
+ } else {
+ // Unable to make fullscreen
+ $element.data( 'isFullscreened', false );
+ return this;
+ }
+ // Add the fullscreen class and data attribute to `element`
+ $element.addClass( fsClass ).data( 'isFullscreened', true );
+ return this;
+ } else {
+ $element.data( 'isFullscreened', false );
+ return this;
+ }
+ }
+
+ /**
+ * Brings the "this" element out of fullscreen.
+ * Check the .data( 'isFullscreened' ) of the return value to check
+ * success or failure, if you're into that sort of thing.
+ * @chainable
+ * @return {jQuery}
+ */
+ function exitFullscreen () {
+ var fullscreenElement = ( document.fullscreenElement ||
+ document.mozFullScreenElement ||
+ document.webkitFullscreenElement ||
+ document.msFullscreenElement );
+
+ // Ensure that we only exit fullscreen if exitFullscreen() is being called on the same element that is currently fullscreen
+ if ( fullscreenElement && this.get(0) === fullscreenElement ) {
+ if ( document.exitFullscreen ) {
+ document.exitFullscreen();
+ } else if ( document.mozCancelFullScreen ) {
+ document.mozCancelFullScreen();
+ } else if ( document.webkitCancelFullScreen ) {
+ document.webkitCancelFullScreen();
+ } else if ( document.msExitFullscreen ) {
+ document.msExitFullscreen();
+ } else {
+ // Unable to cancel fullscreen mode
+ return this;
+ }
+ // We don't need to remove the fullscreen class here,
+ // because it will be removed in handleFullscreenChange.
+ // But we should change the data on the element so the
+ // caller can check for success.
+ this.first().data( 'isFullscreened', false );
+ }
+
+ return this;
+ }
+
+ /**
+ * Set up fullscreen handling and install necessary event handlers.
+ * Return false if fullscreen is not supported.
+ */
+ setupFullscreen = function () {
+ if ( $.support.fullscreen ) {
+ // When the fullscreen mode is changed, trigger the
+ // fullscreen events (and when exiting,
+ // also remove the fullscreen class)
+ $( document ).on( 'fullscreenchange webkitfullscreenchange mozfullscreenchange MSFullscreenChange', handleFullscreenChange);
+ // Convenience wrapper so that one only needs to listen for
+ // 'fullscreenerror', not all of the prefixed versions
+ $( document ).on( 'webkitfullscreenerror mozfullscreenerror MSFullscreenError', function () {
+ $( document ).trigger( $.Event( 'fullscreenerror' ) );
+ } );
+ // Fullscreen has been set up, so always return true
+ setupFullscreen = function () { return true; };
+ return true;
+ } else {
+ // Always return false from now on, since fullscreen is not supported
+ setupFullscreen = function () { return false; };
+ return false;
+ }
+ };
+
+ /**
+ * Set up fullscreen handling if necessary, then make the first element
+ * matching the given selector fullscreen
+ * @chainable
+ * @return {jQuery}
+ */
+ $.fn.enterFullscreen = function () {
+ if ( setupFullscreen() ) {
+ $.fn.enterFullscreen = enterFullscreen;
+ return this.enterFullscreen();
+ } else {
+ $.fn.enterFullscreen = function () { return this; };
+ return this;
+ }
+ };
+
+ /**
+ * Set up fullscreen handling if necessary, then cancel fullscreen mode
+ * for the first element matching the given selector.
+ * @chainable
+ * @return {jQuery}
+ */
+ $.fn.exitFullscreen = function () {
+ if ( setupFullscreen() ) {
+ $.fn.exitFullscreen = exitFullscreen;
+ return this.exitFullscreen();
+ } else {
+ $.fn.exitFullscreen = function () { return this; };
+ return this;
+ }
+ };
+
+ $.support.fullscreen = document.fullscreenEnabled ||
+ document.webkitFullscreenEnabled ||
+ document.mozFullScreenEnabled ||
+ document.msFullscreenEnabled;
+}( jQuery ) );
--- /dev/null
+/**
+* hoverIntent is similar to jQuery's built-in "hover" function except that
+* instead of firing the onMouseOver event immediately, hoverIntent checks
+* to see if the user's mouse has slowed down (beneath the sensitivity
+* threshold) before firing the onMouseOver event.
+*
+* hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+
+* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
+*
+* hoverIntent is currently available for use in all personal or commercial
+* projects under both MIT and GPL licenses. This means that you can choose
+* the license that best suits your project, and use it accordingly.
+*
+* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
+* $("ul li").hoverIntent( showNav , hideNav );
+*
+* // advanced usage receives configuration object only
+* $("ul li").hoverIntent({
+* sensitivity: 7, // number = sensitivity threshold (must be 1 or higher)
+* interval: 100, // number = milliseconds of polling interval
+* over: showNav, // function = onMouseOver callback (required)
+* timeout: 0, // number = milliseconds delay before onMouseOut function call
+* out: hideNav // function = onMouseOut callback (required)
+* });
+*
+* @param f onMouseOver function || An object with configuration options
+* @param g onMouseOut function || Nothing (use configuration options object)
+* @author Brian Cherne <brian@cherne.net>
+*/
+(function($) {
+ $.fn.hoverIntent = function(f,g) {
+ // default configuration options
+ var cfg = {
+ sensitivity: 7,
+ interval: 100,
+ timeout: 0
+ };
+ // override configuration options with user supplied object
+ cfg = $.extend(cfg, g ? { over: f, out: g } : f );
+
+ // instantiate variables
+ // cX, cY = current X and Y position of mouse, updated by mousemove event
+ // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
+ var cX, cY, pX, pY;
+
+ // A private function for getting mouse position
+ var track = function(ev) {
+ cX = ev.pageX;
+ cY = ev.pageY;
+ };
+
+ // A private function for comparing current and previous mouse position
+ var compare = function(ev,ob) {
+ ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
+ // compare mouse positions to see if they've crossed the threshold
+ if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
+ $(ob).unbind("mousemove",track);
+ // set hoverIntent state to true (so mouseOut can be called)
+ ob.hoverIntent_s = 1;
+ return cfg.over.apply(ob,[ev]);
+ } else {
+ // set previous coordinates for next time
+ pX = cX; pY = cY;
+ // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
+ ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
+ }
+ };
+
+ // A private function for delaying the mouseOut function
+ var delay = function(ev,ob) {
+ ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
+ ob.hoverIntent_s = 0;
+ return cfg.out.apply(ob,[ev]);
+ };
+
+ // A private function for handling mouse 'hovering'
+ var handleHover = function(e) {
+ // next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut
+ var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
+ while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } }
+ if ( p == this ) { return false; }
+
+ // copy objects to be passed into t (required for event object to be passed in IE)
+ var ev = $.extend({},e);
+ var ob = this;
+
+ // cancel hoverIntent timer if it exists
+ if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
+
+ // else e.type == "onmouseover"
+ if (e.type == "mouseover") {
+ // set "previous" X and Y position based on initial entry point
+ pX = ev.pageX; pY = ev.pageY;
+ // update "current" X and Y position based on mousemove
+ $(ob).bind("mousemove",track);
+ // start polling interval (self-calling timeout) to compare mouse coordinates over time
+ if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
+
+ // else e.type == "onmouseout"
+ } else {
+ // unbind expensive mousemove event
+ $(ob).unbind("mousemove",track);
+ // if hoverIntent state is true, then call the mouseOut function after the specified delay
+ if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
+ }
+ };
+
+ // bind the function to the two event listeners
+ return this.mouseover(handleHover).mouseout(handleHover);
+ };
+})(jQuery);
\ No newline at end of file
--- /dev/null
+/*
+ * ----------------------------- JSTORAGE -------------------------------------
+ * Simple local storage wrapper to save data on the browser side, supporting
+ * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
+ *
+ * Author: Andris Reinman, andris.reinman@gmail.com
+ * Project homepage: www.jstorage.info
+ *
+ * Licensed under Unlicense:
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * For more information, please refer to <http://unlicense.org/>
+ */
+
+/* global ActiveXObject: false */
+/* jshint browser: true */
+
+(function() {
+ 'use strict';
+
+ var
+ /* jStorage version */
+ JSTORAGE_VERSION = '0.4.12',
+
+ /* detect a dollar object or create one if not found */
+ $ = window.jQuery || window.$ || (window.$ = {}),
+
+ /* check for a JSON handling support */
+ JSON = {
+ parse: window.JSON && (window.JSON.parse || window.JSON.decode) ||
+ String.prototype.evalJSON && function(str) {
+ return String(str).evalJSON();
+ } ||
+ $.parseJSON ||
+ $.evalJSON,
+ stringify: Object.toJSON ||
+ window.JSON && (window.JSON.stringify || window.JSON.encode) ||
+ $.toJSON
+ };
+
+ // Break if no JSON support was found
+ if (typeof JSON.parse !== 'function' || typeof JSON.stringify !== 'function') {
+ throw new Error('No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page');
+ }
+
+ var
+ /* This is the object, that holds the cached values */
+ _storage = {
+ __jstorage_meta: {
+ CRC32: {}
+ }
+ },
+
+ /* Actual browser storage (localStorage or globalStorage['domain']) */
+ _storage_service = {
+ jStorage: '{}'
+ },
+
+ /* DOM element for older IE versions, holds userData behavior */
+ _storage_elm = null,
+
+ /* How much space does the storage take */
+ _storage_size = 0,
+
+ /* which backend is currently used */
+ _backend = false,
+
+ /* onchange observers */
+ _observers = {},
+
+ /* timeout to wait after onchange event */
+ _observer_timeout = false,
+
+ /* last update time */
+ _observer_update = 0,
+
+ /* pubsub observers */
+ _pubsub_observers = {},
+
+ /* skip published items older than current timestamp */
+ _pubsub_last = +new Date(),
+
+ /* Next check for TTL */
+ _ttl_timeout,
+
+ /**
+ * XML encoding and decoding as XML nodes can't be JSON'ized
+ * XML nodes are encoded and decoded if the node is the value to be saved
+ * but not if it's as a property of another object
+ * Eg. -
+ * $.jStorage.set('key', xmlNode); // IS OK
+ * $.jStorage.set('key', {xml: xmlNode}); // NOT OK
+ */
+ _XMLService = {
+
+ /**
+ * Validates a XML node to be XML
+ * based on jQuery.isXML function
+ */
+ isXML: function(elm) {
+ var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
+ return documentElement ? documentElement.nodeName !== 'HTML' : false;
+ },
+
+ /**
+ * Encodes a XML node to string
+ * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
+ */
+ encode: function(xmlNode) {
+ if (!this.isXML(xmlNode)) {
+ return false;
+ }
+ try { // Mozilla, Webkit, Opera
+ return new XMLSerializer().serializeToString(xmlNode);
+ } catch (E1) {
+ try { // IE
+ return xmlNode.xml;
+ } catch (E2) {}
+ }
+ return false;
+ },
+
+ /**
+ * Decodes a XML node from string
+ * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
+ */
+ decode: function(xmlString) {
+ var dom_parser = ('DOMParser' in window && (new DOMParser()).parseFromString) ||
+ (window.ActiveXObject && function(_xmlString) {
+ var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
+ xml_doc.async = 'false';
+ xml_doc.loadXML(_xmlString);
+ return xml_doc;
+ }),
+ resultXML;
+ if (!dom_parser) {
+ return false;
+ }
+ resultXML = dom_parser.call('DOMParser' in window && (new DOMParser()) || window, xmlString, 'text/xml');
+ return this.isXML(resultXML) ? resultXML : false;
+ }
+ };
+
+
+ ////////////////////////// PRIVATE METHODS ////////////////////////
+
+ /**
+ * Initialization function. Detects if the browser supports DOM Storage
+ * or userData behavior and behaves accordingly.
+ */
+ function _init() {
+ /* Check if browser supports localStorage */
+ var localStorageReallyWorks = false;
+ if ('localStorage' in window) {
+ try {
+ window.localStorage.setItem('_tmptest', 'tmpval');
+ localStorageReallyWorks = true;
+ window.localStorage.removeItem('_tmptest');
+ } catch (BogusQuotaExceededErrorOnIos5) {
+ // Thanks be to iOS5 Private Browsing mode which throws
+ // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
+ }
+ }
+
+ if (localStorageReallyWorks) {
+ try {
+ if (window.localStorage) {
+ _storage_service = window.localStorage;
+ _backend = 'localStorage';
+ _observer_update = _storage_service.jStorage_update;
+ }
+ } catch (E3) { /* Firefox fails when touching localStorage and cookies are disabled */ }
+ }
+ /* Check if browser supports globalStorage */
+ else if ('globalStorage' in window) {
+ try {
+ if (window.globalStorage) {
+ if (window.location.hostname == 'localhost') {
+ _storage_service = window.globalStorage['localhost.localdomain'];
+ } else {
+ _storage_service = window.globalStorage[window.location.hostname];
+ }
+ _backend = 'globalStorage';
+ _observer_update = _storage_service.jStorage_update;
+ }
+ } catch (E4) { /* Firefox fails when touching localStorage and cookies are disabled */ }
+ }
+ /* Check if browser supports userData behavior */
+ else {
+ _storage_elm = document.createElement('link');
+ if (_storage_elm.addBehavior) {
+
+ /* Use a DOM element to act as userData storage */
+ _storage_elm.style.behavior = 'url(#default#userData)';
+
+ /* userData element needs to be inserted into the DOM! */
+ document.getElementsByTagName('head')[0].appendChild(_storage_elm);
+
+ try {
+ _storage_elm.load('jStorage');
+ } catch (E) {
+ // try to reset cache
+ _storage_elm.setAttribute('jStorage', '{}');
+ _storage_elm.save('jStorage');
+ _storage_elm.load('jStorage');
+ }
+
+ var data = '{}';
+ try {
+ data = _storage_elm.getAttribute('jStorage');
+ } catch (E5) {}
+
+ try {
+ _observer_update = _storage_elm.getAttribute('jStorage_update');
+ } catch (E6) {}
+
+ _storage_service.jStorage = data;
+ _backend = 'userDataBehavior';
+ } else {
+ _storage_elm = null;
+ return;
+ }
+ }
+
+ // Load data from storage
+ _load_storage();
+
+ // remove dead keys
+ _handleTTL();
+
+ // start listening for changes
+ _setupObserver();
+
+ // initialize publish-subscribe service
+ _handlePubSub();
+
+ // handle cached navigation
+ if ('addEventListener' in window) {
+ window.addEventListener('pageshow', function(event) {
+ if (event.persisted) {
+ _storageObserver();
+ }
+ }, false);
+ }
+ }
+
+ /**
+ * Reload data from storage when needed
+ */
+ function _reloadData() {
+ var data = '{}';
+
+ if (_backend == 'userDataBehavior') {
+ _storage_elm.load('jStorage');
+
+ try {
+ data = _storage_elm.getAttribute('jStorage');
+ } catch (E5) {}
+
+ try {
+ _observer_update = _storage_elm.getAttribute('jStorage_update');
+ } catch (E6) {}
+
+ _storage_service.jStorage = data;
+ }
+
+ _load_storage();
+
+ // remove dead keys
+ _handleTTL();
+
+ _handlePubSub();
+ }
+
+ /**
+ * Sets up a storage change observer
+ */
+ function _setupObserver() {
+ if (_backend == 'localStorage' || _backend == 'globalStorage') {
+ if ('addEventListener' in window) {
+ window.addEventListener('storage', _storageObserver, false);
+ } else {
+ document.attachEvent('onstorage', _storageObserver);
+ }
+ } else if (_backend == 'userDataBehavior') {
+ setInterval(_storageObserver, 1000);
+ }
+ }
+
+ /**
+ * Fired on any kind of data change, needs to check if anything has
+ * really been changed
+ */
+ function _storageObserver() {
+ var updateTime;
+ // cumulate change notifications with timeout
+ clearTimeout(_observer_timeout);
+ _observer_timeout = setTimeout(function() {
+
+ if (_backend == 'localStorage' || _backend == 'globalStorage') {
+ updateTime = _storage_service.jStorage_update;
+ } else if (_backend == 'userDataBehavior') {
+ _storage_elm.load('jStorage');
+ try {
+ updateTime = _storage_elm.getAttribute('jStorage_update');
+ } catch (E5) {}
+ }
+
+ if (updateTime && updateTime != _observer_update) {
+ _observer_update = updateTime;
+ _checkUpdatedKeys();
+ }
+
+ }, 25);
+ }
+
+ /**
+ * Reloads the data and checks if any keys are changed
+ */
+ function _checkUpdatedKeys() {
+ var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
+ newCrc32List;
+
+ _reloadData();
+ newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));
+
+ var key,
+ updated = [],
+ removed = [];
+
+ for (key in oldCrc32List) {
+ if (oldCrc32List.hasOwnProperty(key)) {
+ if (!newCrc32List[key]) {
+ removed.push(key);
+ continue;
+ }
+ if (oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0, 2) == '2.') {
+ updated.push(key);
+ }
+ }
+ }
+
+ for (key in newCrc32List) {
+ if (newCrc32List.hasOwnProperty(key)) {
+ if (!oldCrc32List[key]) {
+ updated.push(key);
+ }
+ }
+ }
+
+ _fireObservers(updated, 'updated');
+ _fireObservers(removed, 'deleted');
+ }
+
+ /**
+ * Fires observers for updated keys
+ *
+ * @param {Array|String} keys Array of key names or a key
+ * @param {String} action What happened with the value (updated, deleted, flushed)
+ */
+ function _fireObservers(keys, action) {
+ keys = [].concat(keys || []);
+
+ var i, j, len, jlen;
+
+ if (action == 'flushed') {
+ keys = [];
+ for (var key in _observers) {
+ if (_observers.hasOwnProperty(key)) {
+ keys.push(key);
+ }
+ }
+ action = 'deleted';
+ }
+ for (i = 0, len = keys.length; i < len; i++) {
+ if (_observers[keys[i]]) {
+ for (j = 0, jlen = _observers[keys[i]].length; j < jlen; j++) {
+ _observers[keys[i]][j](keys[i], action);
+ }
+ }
+ if (_observers['*']) {
+ for (j = 0, jlen = _observers['*'].length; j < jlen; j++) {
+ _observers['*'][j](keys[i], action);
+ }
+ }
+ }
+ }
+
+ /**
+ * Publishes key change to listeners
+ */
+ function _publishChange() {
+ var updateTime = (+new Date()).toString();
+
+ if (_backend == 'localStorage' || _backend == 'globalStorage') {
+ try {
+ _storage_service.jStorage_update = updateTime;
+ } catch (E8) {
+ // safari private mode has been enabled after the jStorage initialization
+ _backend = false;
+ }
+ } else if (_backend == 'userDataBehavior') {
+ _storage_elm.setAttribute('jStorage_update', updateTime);
+ _storage_elm.save('jStorage');
+ }
+
+ _storageObserver();
+ }
+
+ /**
+ * Loads the data from the storage based on the supported mechanism
+ */
+ function _load_storage() {
+ /* if jStorage string is retrieved, then decode it */
+ if (_storage_service.jStorage) {
+ try {
+ _storage = JSON.parse(String(_storage_service.jStorage));
+ } catch (E6) {
+ _storage_service.jStorage = '{}';
+ }
+ } else {
+ _storage_service.jStorage = '{}';
+ }
+ _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
+
+ if (!_storage.__jstorage_meta) {
+ _storage.__jstorage_meta = {};
+ }
+ if (!_storage.__jstorage_meta.CRC32) {
+ _storage.__jstorage_meta.CRC32 = {};
+ }
+ }
+
+ /**
+ * This functions provides the 'save' mechanism to store the jStorage object
+ */
+ function _save() {
+ _dropOldEvents(); // remove expired events
+ try {
+ _storage_service.jStorage = JSON.stringify(_storage);
+ // If userData is used as the storage engine, additional
+ if (_storage_elm) {
+ _storage_elm.setAttribute('jStorage', _storage_service.jStorage);
+ _storage_elm.save('jStorage');
+ }
+ _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
+ } catch (E7) { /* probably cache is full, nothing is saved this way*/ }
+ }
+
+ /**
+ * Function checks if a key is set and is string or numberic
+ *
+ * @param {String} key Key name
+ */
+ function _checkKey(key) {
+ if (typeof key != 'string' && typeof key != 'number') {
+ throw new TypeError('Key name must be string or numeric');
+ }
+ if (key == '__jstorage_meta') {
+ throw new TypeError('Reserved key name');
+ }
+ return true;
+ }
+
+ /**
+ * Removes expired keys
+ */
+ function _handleTTL() {
+ var curtime, i, TTL, CRC32, nextExpire = Infinity,
+ changed = false,
+ deleted = [];
+
+ clearTimeout(_ttl_timeout);
+
+ if (!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != 'object') {
+ // nothing to do here
+ return;
+ }
+
+ curtime = +new Date();
+ TTL = _storage.__jstorage_meta.TTL;
+
+ CRC32 = _storage.__jstorage_meta.CRC32;
+ for (i in TTL) {
+ if (TTL.hasOwnProperty(i)) {
+ if (TTL[i] <= curtime) {
+ delete TTL[i];
+ delete CRC32[i];
+ delete _storage[i];
+ changed = true;
+ deleted.push(i);
+ } else if (TTL[i] < nextExpire) {
+ nextExpire = TTL[i];
+ }
+ }
+ }
+
+ // set next check
+ if (nextExpire != Infinity) {
+ _ttl_timeout = setTimeout(_handleTTL, Math.min(nextExpire - curtime, 0x7FFFFFFF));
+ }
+
+ // save changes
+ if (changed) {
+ _save();
+ _publishChange();
+ _fireObservers(deleted, 'deleted');
+ }
+ }
+
+ /**
+ * Checks if there's any events on hold to be fired to listeners
+ */
+ function _handlePubSub() {
+ var i, len;
+ if (!_storage.__jstorage_meta.PubSub) {
+ return;
+ }
+ var pubelm,
+ _pubsubCurrent = _pubsub_last,
+ needFired = [];
+
+ for (i = len = _storage.__jstorage_meta.PubSub.length - 1; i >= 0; i--) {
+ pubelm = _storage.__jstorage_meta.PubSub[i];
+ if (pubelm[0] > _pubsub_last) {
+ _pubsubCurrent = pubelm[0];
+ needFired.unshift(pubelm);
+ }
+ }
+
+ for (i = needFired.length - 1; i >= 0; i--) {
+ _fireSubscribers(needFired[i][1], needFired[i][2]);
+ }
+
+ _pubsub_last = _pubsubCurrent;
+ }
+
+ /**
+ * Fires all subscriber listeners for a pubsub channel
+ *
+ * @param {String} channel Channel name
+ * @param {Mixed} payload Payload data to deliver
+ */
+ function _fireSubscribers(channel, payload) {
+ if (_pubsub_observers[channel]) {
+ for (var i = 0, len = _pubsub_observers[channel].length; i < len; i++) {
+ // send immutable data that can't be modified by listeners
+ try {
+ _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
+ } catch (E) {}
+ }
+ }
+ }
+
+ /**
+ * Remove old events from the publish stream (at least 2sec old)
+ */
+ function _dropOldEvents() {
+ if (!_storage.__jstorage_meta.PubSub) {
+ return;
+ }
+
+ var retire = +new Date() - 2000;
+
+ for (var i = 0, len = _storage.__jstorage_meta.PubSub.length; i < len; i++) {
+ if (_storage.__jstorage_meta.PubSub[i][0] <= retire) {
+ // deleteCount is needed for IE6
+ _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
+ break;
+ }
+ }
+
+ if (!_storage.__jstorage_meta.PubSub.length) {
+ delete _storage.__jstorage_meta.PubSub;
+ }
+
+ }
+
+ /**
+ * Publish payload to a channel
+ *
+ * @param {String} channel Channel name
+ * @param {Mixed} payload Payload to send to the subscribers
+ */
+ function _publish(channel, payload) {
+ if (!_storage.__jstorage_meta) {
+ _storage.__jstorage_meta = {};
+ }
+ if (!_storage.__jstorage_meta.PubSub) {
+ _storage.__jstorage_meta.PubSub = [];
+ }
+
+ _storage.__jstorage_meta.PubSub.unshift([+new Date(), channel, payload]);
+
+ _save();
+ _publishChange();
+ }
+
+
+ /**
+ * JS Implementation of MurmurHash2
+ *
+ * SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
+ *
+ * @author <a href='mailto:gary.court@gmail.com'>Gary Court</a>
+ * @see http://github.com/garycourt/murmurhash-js
+ * @author <a href='mailto:aappleby@gmail.com'>Austin Appleby</a>
+ * @see http://sites.google.com/site/murmurhash/
+ *
+ * @param {string} str ASCII only
+ * @param {number} seed Positive integer only
+ * @return {number} 32-bit positive integer hash
+ */
+
+ function murmurhash2_32_gc(str, seed) {
+ var
+ l = str.length,
+ h = seed ^ l,
+ i = 0,
+ k;
+
+ while (l >= 4) {
+ k =
+ ((str.charCodeAt(i) & 0xff)) |
+ ((str.charCodeAt(++i) & 0xff) << 8) |
+ ((str.charCodeAt(++i) & 0xff) << 16) |
+ ((str.charCodeAt(++i) & 0xff) << 24);
+
+ k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+ k ^= k >>> 24;
+ k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+
+ h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
+
+ l -= 4;
+ ++i;
+ }
+
+ switch (l) {
+ case 3:
+ h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
+ /* falls through */
+ case 2:
+ h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
+ /* falls through */
+ case 1:
+ h ^= (str.charCodeAt(i) & 0xff);
+ h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+ }
+
+ h ^= h >>> 13;
+ h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+ h ^= h >>> 15;
+
+ return h >>> 0;
+ }
+
+ ////////////////////////// PUBLIC INTERFACE /////////////////////////
+
+ $.jStorage = {
+ /* Version number */
+ version: JSTORAGE_VERSION,
+
+ /**
+ * Sets a key's value.
+ *
+ * @param {String} key Key to set. If this value is not set or not
+ * a string an exception is raised.
+ * @param {Mixed} value Value to set. This can be any value that is JSON
+ * compatible (Numbers, Strings, Objects etc.).
+ * @param {Object} [options] - possible options to use
+ * @param {Number} [options.TTL] - optional TTL value, in milliseconds
+ * @return {Mixed} the used value
+ */
+ set: function(key, value, options) {
+ _checkKey(key);
+
+ options = options || {};
+
+ // undefined values are deleted automatically
+ if (typeof value == 'undefined') {
+ this.deleteKey(key);
+ return value;
+ }
+
+ if (_XMLService.isXML(value)) {
+ value = {
+ _is_xml: true,
+ xml: _XMLService.encode(value)
+ };
+ } else if (typeof value == 'function') {
+ return undefined; // functions can't be saved!
+ } else if (value && typeof value == 'object') {
+ // clone the object before saving to _storage tree
+ value = JSON.parse(JSON.stringify(value));
+ }
+
+ _storage[key] = value;
+
+ _storage.__jstorage_meta.CRC32[key] = '2.' + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);
+
+ this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange
+
+ _fireObservers(key, 'updated');
+ return value;
+ },
+
+ /**
+ * Looks up a key in cache
+ *
+ * @param {String} key - Key to look up.
+ * @param {mixed} def - Default value to return, if key didn't exist.
+ * @return {Mixed} the key value, default value or null
+ */
+ get: function(key, def) {
+ _checkKey(key);
+ if (key in _storage) {
+ if (_storage[key] && typeof _storage[key] == 'object' && _storage[key]._is_xml) {
+ return _XMLService.decode(_storage[key].xml);
+ } else {
+ return _storage[key];
+ }
+ }
+ return typeof(def) == 'undefined' ? null : def;
+ },
+
+ /**
+ * Deletes a key from cache.
+ *
+ * @param {String} key - Key to delete.
+ * @return {Boolean} true if key existed or false if it didn't
+ */
+ deleteKey: function(key) {
+ _checkKey(key);
+ if (key in _storage) {
+ delete _storage[key];
+ // remove from TTL list
+ if (typeof _storage.__jstorage_meta.TTL == 'object' &&
+ key in _storage.__jstorage_meta.TTL) {
+ delete _storage.__jstorage_meta.TTL[key];
+ }
+
+ delete _storage.__jstorage_meta.CRC32[key];
+
+ _save();
+ _publishChange();
+ _fireObservers(key, 'deleted');
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Sets a TTL for a key, or remove it if ttl value is 0 or below
+ *
+ * @param {String} key - key to set the TTL for
+ * @param {Number} ttl - TTL timeout in milliseconds
+ * @return {Boolean} true if key existed or false if it didn't
+ */
+ setTTL: function(key, ttl) {
+ var curtime = +new Date();
+ _checkKey(key);
+ ttl = Number(ttl) || 0;
+ if (key in _storage) {
+
+ if (!_storage.__jstorage_meta.TTL) {
+ _storage.__jstorage_meta.TTL = {};
+ }
+
+ // Set TTL value for the key
+ if (ttl > 0) {
+ _storage.__jstorage_meta.TTL[key] = curtime + ttl;
+ } else {
+ delete _storage.__jstorage_meta.TTL[key];
+ }
+
+ _save();
+
+ _handleTTL();
+
+ _publishChange();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
+ *
+ * @param {String} key Key to check
+ * @return {Number} Remaining TTL in milliseconds
+ */
+ getTTL: function(key) {
+ var curtime = +new Date(),
+ ttl;
+ _checkKey(key);
+ if (key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]) {
+ ttl = _storage.__jstorage_meta.TTL[key] - curtime;
+ return ttl || 0;
+ }
+ return 0;
+ },
+
+ /**
+ * Deletes everything in cache.
+ *
+ * @return {Boolean} Always true
+ */
+ flush: function() {
+ _storage = {
+ __jstorage_meta: {
+ CRC32: {}
+ }
+ };
+ _save();
+ _publishChange();
+ _fireObservers(null, 'flushed');
+ return true;
+ },
+
+ /**
+ * Returns a read-only copy of _storage
+ *
+ * @return {Object} Read-only copy of _storage
+ */
+ storageObj: function() {
+ function F() {}
+ F.prototype = _storage;
+ return new F();
+ },
+
+ /**
+ * Returns an index of all used keys as an array
+ * ['key1', 'key2',..'keyN']
+ *
+ * @return {Array} Used keys
+ */
+ index: function() {
+ var index = [],
+ i;
+ for (i in _storage) {
+ if (_storage.hasOwnProperty(i) && i != '__jstorage_meta') {
+ index.push(i);
+ }
+ }
+ return index;
+ },
+
+ /**
+ * How much space in bytes does the storage take?
+ *
+ * @return {Number} Storage size in chars (not the same as in bytes,
+ * since some chars may take several bytes)
+ */
+ storageSize: function() {
+ return _storage_size;
+ },
+
+ /**
+ * Which backend is currently in use?
+ *
+ * @return {String} Backend name
+ */
+ currentBackend: function() {
+ return _backend;
+ },
+
+ /**
+ * Test if storage is available
+ *
+ * @return {Boolean} True if storage can be used
+ */
+ storageAvailable: function() {
+ return !!_backend;
+ },
+
+ /**
+ * Register change listeners
+ *
+ * @param {String} key Key name
+ * @param {Function} callback Function to run when the key changes
+ */
+ listenKeyChange: function(key, callback) {
+ _checkKey(key);
+ if (!_observers[key]) {
+ _observers[key] = [];
+ }
+ _observers[key].push(callback);
+ },
+
+ /**
+ * Remove change listeners
+ *
+ * @param {String} key Key name to unregister listeners against
+ * @param {Function} [callback] If set, unregister the callback, if not - unregister all
+ */
+ stopListening: function(key, callback) {
+ _checkKey(key);
+
+ if (!_observers[key]) {
+ return;
+ }
+
+ if (!callback) {
+ delete _observers[key];
+ return;
+ }
+
+ for (var i = _observers[key].length - 1; i >= 0; i--) {
+ if (_observers[key][i] == callback) {
+ _observers[key].splice(i, 1);
+ }
+ }
+ },
+
+ /**
+ * Subscribe to a Publish/Subscribe event stream
+ *
+ * @param {String} channel Channel name
+ * @param {Function} callback Function to run when the something is published to the channel
+ */
+ subscribe: function(channel, callback) {
+ channel = (channel || '').toString();
+ if (!channel) {
+ throw new TypeError('Channel not defined');
+ }
+ if (!_pubsub_observers[channel]) {
+ _pubsub_observers[channel] = [];
+ }
+ _pubsub_observers[channel].push(callback);
+ },
+
+ /**
+ * Publish data to an event stream
+ *
+ * @param {String} channel Channel name
+ * @param {Mixed} payload Payload to deliver
+ */
+ publish: function(channel, payload) {
+ channel = (channel || '').toString();
+ if (!channel) {
+ throw new TypeError('Channel not defined');
+ }
+
+ _publish(channel, payload);
+ },
+
+ /**
+ * Reloads the data from browser storage
+ */
+ reInit: function() {
+ _reloadData();
+ },
+
+ /**
+ * Removes reference from global objects and saves it as jStorage
+ *
+ * @param {Boolean} option if needed to save object as simple 'jStorage' in windows context
+ */
+ noConflict: function(saveInGlobal) {
+ delete window.$.jStorage;
+
+ if (saveInGlobal) {
+ window.jStorage = this;
+ }
+
+ return this;
+ }
+ };
+
+ // Initialize jStorage
+ _init();
+
+})();
--- /dev/null
+/*!
+ * MockJax - jQuery Plugin to Mock Ajax requests
+ *
+ * Version: 1.4.0
+ * Released: 2011-02-04
+ * Source: http://github.com/appendto/jquery-mockjax
+ * Docs: http://enterprisejquery.com/2010/07/mock-your-ajax-requests-with-mockjax-for-rapid-development
+ * Plugin: mockjax
+ * Author: Jonathan Sharp (http://jdsharp.com)
+ * License: MIT,GPL
+ *
+ * Copyright (c) 2010 appendTo LLC.
+ * Dual licensed under the MIT or GPL licenses.
+ * http://appendto.com/open-source-licenses
+ */
+(function($) {
+ var _ajax = $.ajax,
+ mockHandlers = [];
+
+ function parseXML(xml) {
+ if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
+ DOMParser = function() { };
+ DOMParser.prototype.parseFromString = function( xmlString ) {
+ var doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ doc.loadXML( xmlString );
+ return doc;
+ };
+ }
+
+ try {
+ var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
+ if ( $.isXMLDoc( xmlDoc ) ) {
+ var err = $('parsererror', xmlDoc);
+ if ( err.length == 1 ) {
+ throw('Error: ' + $(xmlDoc).text() );
+ }
+ } else {
+ throw('Unable to parse XML');
+ }
+ } catch( e ) {
+ var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
+ $(document).trigger('xmlParseError', [ msg ]);
+ return undefined;
+ }
+ return xmlDoc;
+ }
+
+ $.extend({
+ ajax: function(origSettings) {
+ var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings),
+ mock = false;
+ // Iterate over our mock handlers (in registration order) until we find
+ // one that is willing to intercept the request
+ $.each(mockHandlers, function(k, v) {
+ if ( !mockHandlers[k] ) {
+ return;
+ }
+ var m = null;
+ // If the mock was registered with a function, let the function decide if we
+ // want to mock this request
+ if ( $.isFunction(mockHandlers[k]) ) {
+ m = mockHandlers[k](s);
+ } else {
+ m = mockHandlers[k];
+ // Inspect the URL of the request and check if the mock handler's url
+ // matches the url for this ajax request
+ if ( $.isFunction(m.url.test) ) {
+ // The user provided a regex for the url, test it
+ if ( !m.url.test( s.url ) ) {
+ m = null;
+ }
+ } else {
+ // Look for a simple wildcard '*' or a direct URL match
+ var star = m.url.indexOf('*');
+ if ( ( m.url != '*' && m.url != s.url && star == -1 ) ||
+ ( star > -1 && m.url.substr(0, star) != s.url.substr(0, star) ) ) {
+ // The url we tested did not match the wildcard *
+ m = null;
+ }
+ }
+ if ( m ) {
+ // Inspect the data submitted in the request (either POST body or GET query string)
+ if ( m.data && s.data ) {
+ var identical = false;
+ // Deep inspect the identity of the objects
+ (function ident(mock, live) {
+ // Test for situations where the data is a querystring (not an object)
+ if (typeof live === 'string') {
+ // Querystring may be a regex
+ identical = $.isFunction( mock.test ) ? mock.test(live) : mock == live;
+ return identical;
+ }
+ $.each(mock, function(k, v) {
+ if ( live[k] === undefined ) {
+ identical = false;
+ return false;
+ } else {
+ identical = true;
+ if ( typeof live[k] == 'object' ) {
+ return ident(mock[k], live[k]);
+ } else {
+ if ( $.isFunction( mock[k].test ) ) {
+ identical = mock[k].test(live[k]);
+ } else {
+ identical = ( mock[k] == live[k] );
+ }
+ return identical;
+ }
+ }
+ });
+ })(m.data, s.data);
+ // They're not identical, do not mock this request
+ if ( identical == false ) {
+ m = null;
+ }
+ }
+ // Inspect the request type
+ if ( m && m.type && m.type != s.type ) {
+ // The request type doesn't match (GET vs. POST)
+ m = null;
+ }
+ }
+ }
+ if ( m ) {
+ mock = true;
+
+ // Handle console logging
+ var c = $.extend({}, $.mockjaxSettings, m);
+ if ( c.log && $.isFunction(c.log) ) {
+ c.log('MOCK ' + s.type.toUpperCase() + ': ' + s.url, $.extend({}, s));
+ }
+
+ var jsre = /=\?(&|$)/, jsc = (new Date()).getTime();
+
+ // Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
+ // because there isn't an easy hook for the cross domain script tag of jsonp
+ if ( s.dataType === "jsonp" ) {
+ if ( s.type.toUpperCase() === "GET" ) {
+ if ( !jsre.test( s.url ) ) {
+ s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+ }
+ } else if ( !s.data || !jsre.test(s.data) ) {
+ s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+ }
+ s.dataType = "json";
+ }
+
+ // Build temporary JSONP function
+ if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
+ jsonp = s.jsonpCallback || ("jsonp" + jsc++);
+
+ // Replace the =? sequence both in the query string and the data
+ if ( s.data ) {
+ s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+ }
+
+ s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+ // We need to make sure
+ // that a JSONP style response is executed properly
+ s.dataType = "script";
+
+ // Handle JSONP-style loading
+ window[ jsonp ] = window[ jsonp ] || function( tmp ) {
+ data = tmp;
+ success();
+ complete();
+ // Garbage collect
+ window[ jsonp ] = undefined;
+
+ try {
+ delete window[ jsonp ];
+ } catch(e) {}
+
+ if ( head ) {
+ head.removeChild( script );
+ }
+ };
+ }
+
+ var rurl = /^(\w+:)?\/\/([^\/?#]+)/,
+ parts = rurl.exec( s.url ),
+ remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
+
+ // Test if we are going to create a script tag (if so, intercept & mock)
+ if ( s.dataType === "script" && s.type.toUpperCase() === "GET" && remote ) {
+ // Synthesize the mock request for adding a script tag
+ var callbackContext = origSettings && origSettings.context || s;
+
+ function success() {
+ // If a local callback was specified, fire it and pass it the data
+ if ( s.success ) {
+ s.success.call( callbackContext, ( m.response ? m.response.toString() : m.responseText || ''), status, {} );
+ }
+
+ // Fire the global callback
+ if ( s.global ) {
+ trigger( "ajaxSuccess", [{}, s] );
+ }
+ }
+
+ function complete() {
+ // Process result
+ if ( s.complete ) {
+ s.complete.call( callbackContext, {} , status );
+ }
+
+ // The request was completed
+ if ( s.global ) {
+ trigger( "ajaxComplete", [{}, s] );
+ }
+
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+
+ function trigger(type, args) {
+ (s.context ? jQuery(s.context) : jQuery.event).trigger(type, args);
+ }
+
+ if ( m.response && $.isFunction(m.response) ) {
+ m.response(origSettings);
+ } else {
+ $.globalEval(m.responseText);
+ }
+ success();
+ complete();
+ return false;
+ }
+ mock = _ajax.call($, $.extend(true, {}, origSettings, {
+ // Mock the XHR object
+ xhr: function() {
+ // Extend with our default mockjax settings
+ m = $.extend({}, $.mockjaxSettings, m);
+
+ if ( m.contentType ) {
+ m.headers['content-type'] = m.contentType;
+ }
+
+ // Return our mock xhr object
+ return {
+ status: m.status,
+ readyState: 1,
+ open: function() { },
+ send: function() {
+ // This is a substitute for < 1.4 which lacks $.proxy
+ var process = (function(that) {
+ return function() {
+ return (function() {
+ // The request has returned
+ this.status = m.status;
+ this.readyState = 4;
+
+ // We have an executable function, call it to give
+ // the mock handler a chance to update it's data
+ if ( $.isFunction(m.response) ) {
+ m.response(origSettings);
+ }
+ // Copy over our mock to our xhr object before passing control back to
+ // jQuery's onreadystatechange callback
+ if ( s.dataType == 'json' && ( typeof m.responseText == 'object' ) ) {
+ this.responseText = JSON.stringify(m.responseText);
+ } else if ( s.dataType == 'xml' ) {
+ if ( typeof m.responseXML == 'string' ) {
+ this.responseXML = parseXML(m.responseXML);
+ } else {
+ this.responseXML = m.responseXML;
+ }
+ } else {
+ this.responseText = m.responseText;
+ }
+ // jQuery < 1.4 doesn't have onreadystate change for xhr
+ if ( $.isFunction(this.onreadystatechange) ) {
+ this.onreadystatechange( m.isTimeout ? 'timeout' : undefined );
+ }
+ }).apply(that);
+ };
+ })(this);
+
+ if ( m.proxy ) {
+ // We're proxying this request and loading in an external file instead
+ _ajax({
+ global: false,
+ url: m.proxy,
+ type: m.proxyType,
+ data: m.data,
+ dataType: s.dataType,
+ complete: function(xhr, txt) {
+ m.responseXML = xhr.responseXML;
+ m.responseText = xhr.responseText;
+ this.responseTimer = setTimeout(process, m.responseTime || 0);
+ }
+ });
+ } else {
+ // type == 'POST' || 'GET' || 'DELETE'
+ if ( s.async === false ) {
+ // TODO: Blocking delay
+ process();
+ } else {
+ this.responseTimer = setTimeout(process, m.responseTime || 50);
+ }
+ }
+ },
+ abort: function() {
+ clearTimeout(this.responseTimer);
+ },
+ setRequestHeader: function() { },
+ getResponseHeader: function(header) {
+ // 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
+ if ( m.headers && m.headers[header] ) {
+ // Return arbitrary headers
+ return m.headers[header];
+ } else if ( header.toLowerCase() == 'last-modified' ) {
+ return m.lastModified || (new Date()).toString();
+ } else if ( header.toLowerCase() == 'etag' ) {
+ return m.etag || '';
+ } else if ( header.toLowerCase() == 'content-type' ) {
+ return m.contentType || 'text/plain';
+ }
+ },
+ getAllResponseHeaders: function() {
+ var headers = '';
+ $.each(m.headers, function(k, v) {
+ headers += k + ': ' + v + "\n";
+ });
+ return headers;
+ }
+ };
+ }
+ }));
+ return false;
+ }
+ });
+ // We don't have a mock request, trigger a normal request
+ if ( !mock ) {
+ return _ajax.apply($, arguments);
+ } else {
+ return mock;
+ }
+ }
+ });
+
+ $.mockjaxSettings = {
+ //url: null,
+ //type: 'GET',
+ log: function(msg) {
+ window['console'] && window.console.log && window.console.log(msg);
+ },
+ status: 200,
+ responseTime: 500,
+ isTimeout: false,
+ contentType: 'text/plain',
+ response: '',
+ responseText: '',
+ responseXML: '',
+ proxy: '',
+ proxyType: 'GET',
+
+ lastModified: null,
+ etag: '',
+ headers: {
+ etag: 'IJF@H#@923uf8023hFO@I#H#',
+ 'content-type' : 'text/plain'
+ }
+ };
+
+ $.mockjax = function(settings) {
+ var i = mockHandlers.length;
+ mockHandlers[i] = settings;
+ return i;
+ };
+ $.mockjaxClear = function(i) {
+ if ( arguments.length == 1 ) {
+ mockHandlers[i] = null;
+ } else {
+ mockHandlers = [];
+ }
+ };
+})(jQuery);
--- /dev/null
+/*!
+ * jQuery xmlDOM Plugin v1.0
+ * http://outwestmedia.com/jquery-plugins/xmldom/
+ *
+ * Released: 2009-04-06
+ * Version: 1.0
+ *
+ * Copyright (c) 2009 Jonathan Sharp, Out West Media LLC.
+ * Dual licensed under the MIT and GPL licenses.
+ * http://docs.jquery.com/License
+ */
+(function($) {
+ // IE DOMParser wrapper
+ if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
+ DOMParser = function() { };
+ DOMParser.prototype.parseFromString = function( xmlString ) {
+ var doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ doc.loadXML( xmlString );
+ return doc;
+ };
+ }
+
+ $.xmlDOM = function(xml, onErrorFn) {
+ try {
+ var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
+ if ( $.isXMLDoc( xmlDoc ) ) {
+ var err = $('parsererror', xmlDoc);
+ if ( err.length == 1 ) {
+ throw('Error: ' + $(xmlDoc).text() );
+ }
+ } else {
+ throw('Unable to parse XML');
+ }
+ } catch( e ) {
+ var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
+ if ( $.isFunction( onErrorFn ) ) {
+ onErrorFn( msg );
+ } else {
+ $(document).trigger('xmlParseError', [ msg ]);
+ }
+ return $([]);
+ }
+ return $( xmlDoc );
+ };
+})(jQuery);
\ No newline at end of file
+++ /dev/null
-/*
- * jQuery Asynchronous Plugin 1.0
- *
- * Copyright (c) 2008 Vincent Robert (genezys.net)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- */
-(function($){
-
-// opts.delay : (default 10) delay between async call in ms
-// opts.bulk : (default 500) delay during which the loop can continue synchronously without yielding the CPU
-// opts.test : (default true) function to test in the while test part
-// opts.loop : (default empty) function to call in the while loop part
-// opts.end : (default empty) function to call at the end of the while loop
-$.whileAsync = function(opts) {
- var delay = Math.abs(opts.delay) || 10,
- bulk = isNaN(opts.bulk) ? 500 : Math.abs(opts.bulk),
- test = opts.test || function(){ return true; },
- loop = opts.loop || function(){},
- end = opts.end || function(){};
-
- (function(){
-
- var t = false,
- begin = new Date();
-
- while( t = test() ) {
- loop();
- if( bulk === 0 || (new Date() - begin) > bulk ) {
- break;
- }
- }
- if( t ) {
- setTimeout(arguments.callee, delay);
- }
- else {
- end();
- }
-
- })();
-};
-
-// opts.delay : (default 10) delay between async call in ms
-// opts.bulk : (default 500) delay during which the loop can continue synchronously without yielding the CPU
-// opts.loop : (default empty) function to call in the each loop part, signature: function(index, value) this = value
-// opts.end : (default empty) function to call at the end of the each loop
-$.eachAsync = function(array, opts) {
- var i = 0,
- l = array.length,
- loop = opts.loop || function(){};
-
- $.whileAsync(
- $.extend(opts, {
- test: function() { return i < l; },
- loop: function() {
- var val = array[i];
- return loop.call(val, i++, val);
- }
- })
- );
-};
-
-$.fn.eachAsync = function(opts) {
- $.eachAsync(this, opts);
- return this;
-}
-
-})(jQuery);
\ No newline at end of file
+++ /dev/null
-/*!
- * jQuery throttle / debounce - v1.1 - 3/7/2010
- * http://benalman.com/projects/jquery-throttle-debounce-plugin/
- *
- * Copyright (c) 2010 "Cowboy" Ben Alman
- * Dual licensed under the MIT and GPL licenses.
- * http://benalman.com/about/license/
- */
-
-// Script: jQuery throttle / debounce: Sometimes, less is more!
-//
-// *Version: 1.1, Last updated: 3/7/2010*
-//
-// Project Home - http://benalman.com/projects/jquery-throttle-debounce-plugin/
-// GitHub - http://github.com/cowboy/jquery-throttle-debounce/
-// Source - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.js
-// (Minified) - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.min.js (0.7kb)
-//
-// About: License
-//
-// Copyright (c) 2010 "Cowboy" Ben Alman,
-// Dual licensed under the MIT and GPL licenses.
-// http://benalman.com/about/license/
-//
-// About: Examples
-//
-// These working examples, complete with fully commented code, illustrate a few
-// ways in which this plugin can be used.
-//
-// Throttle - http://benalman.com/code/projects/jquery-throttle-debounce/examples/throttle/
-// Debounce - http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/
-//
-// About: Support and Testing
-//
-// Information about what version or versions of jQuery this plugin has been
-// tested with, what browsers it has been tested in, and where the unit tests
-// reside (so you can test it yourself).
-//
-// jQuery Versions - none, 1.3.2, 1.4.2
-// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome 4-5, Opera 9.6-10.1.
-// Unit Tests - http://benalman.com/code/projects/jquery-throttle-debounce/unit/
-//
-// About: Release History
-//
-// 1.1 - (3/7/2010) Fixed a bug in <jQuery.throttle> where trailing callbacks
-// executed later than they should. Reworked a fair amount of internal
-// logic as well.
-// 1.0 - (3/6/2010) Initial release as a stand-alone project. Migrated over
-// from jquery-misc repo v0.4 to jquery-throttle repo v1.0, added the
-// no_trailing throttle parameter and debounce functionality.
-//
-// Topic: Note for non-jQuery users
-//
-// jQuery isn't actually required for this plugin, because nothing internal
-// uses any jQuery methods or properties. jQuery is just used as a namespace
-// under which these methods can exist.
-//
-// Since jQuery isn't actually required for this plugin, if jQuery doesn't exist
-// when this plugin is loaded, the method described below will be created in
-// the `Cowboy` namespace. Usage will be exactly the same, but instead of
-// $.method() or jQuery.method(), you'll need to use Cowboy.method().
-
-(function(window,undefined){
- '$:nomunge'; // Used by YUI compressor.
-
- // Since jQuery really isn't required for this plugin, use `jQuery` as the
- // namespace only if it already exists, otherwise use the `Cowboy` namespace,
- // creating it if necessary.
- var $ = window.jQuery || window.Cowboy || ( window.Cowboy = {} ),
-
- // Internal method reference.
- jq_throttle;
-
- // Method: jQuery.throttle
- //
- // Throttle execution of a function. Especially useful for rate limiting
- // execution of handlers on events like resize and scroll. If you want to
- // rate-limit execution of a function to a single time, see the
- // <jQuery.debounce> method.
- //
- // In this visualization, | is a throttled-function call and X is the actual
- // callback execution:
- //
- // > Throttled with `no_trailing` specified as false or unspecified:
- // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
- // > X X X X X X X X X X X X
- // >
- // > Throttled with `no_trailing` specified as true:
- // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
- // > X X X X X X X X X X
- //
- // Usage:
- //
- // > var throttled = jQuery.throttle( delay, [ no_trailing, ] callback );
- // >
- // > jQuery('selector').bind( 'someevent', throttled );
- // > jQuery('selector').unbind( 'someevent', throttled );
- //
- // This also works in jQuery 1.4+:
- //
- // > jQuery('selector').bind( 'someevent', jQuery.throttle( delay, [ no_trailing, ] callback ) );
- // > jQuery('selector').unbind( 'someevent', callback );
- //
- // Arguments:
- //
- // delay - (Number) A zero-or-greater delay in milliseconds. For event
- // callbacks, values around 100 or 250 (or even higher) are most useful.
- // no_trailing - (Boolean) Optional, defaults to false. If no_trailing is
- // true, callback will only execute every `delay` milliseconds while the
- // throttled-function is being called. If no_trailing is false or
- // unspecified, callback will be executed one final time after the last
- // throttled-function call. (After the throttled-function has not been
- // called for `delay` milliseconds, the internal counter is reset)
- // callback - (Function) A function to be executed after delay milliseconds.
- // The `this` context and all arguments are passed through, as-is, to
- // `callback` when the throttled-function is executed.
- //
- // Returns:
- //
- // (Function) A new, throttled, function.
-
- $.throttle = jq_throttle = function( delay, no_trailing, callback, debounce_mode ) {
- // After wrapper has stopped being called, this timeout ensures that
- // `callback` is executed at the proper times in `throttle` and `end`
- // debounce modes.
- var timeout_id,
-
- // Keep track of the last time `callback` was executed.
- last_exec = 0;
-
- // `no_trailing` defaults to falsy.
- if ( typeof no_trailing !== 'boolean' ) {
- debounce_mode = callback;
- callback = no_trailing;
- no_trailing = undefined;
- }
-
- // The `wrapper` function encapsulates all of the throttling / debouncing
- // functionality and when executed will limit the rate at which `callback`
- // is executed.
- function wrapper() {
- var that = this,
- elapsed = +new Date() - last_exec,
- args = arguments;
-
- // Execute `callback` and update the `last_exec` timestamp.
- function exec() {
- last_exec = +new Date();
- callback.apply( that, args );
- };
-
- // If `debounce_mode` is true (at_begin) this is used to clear the flag
- // to allow future `callback` executions.
- function clear() {
- timeout_id = undefined;
- };
-
- if ( debounce_mode && !timeout_id ) {
- // Since `wrapper` is being called for the first time and
- // `debounce_mode` is true (at_begin), execute `callback`.
- exec();
- }
-
- // Clear any existing timeout.
- timeout_id && clearTimeout( timeout_id );
-
- if ( debounce_mode === undefined && elapsed > delay ) {
- // In throttle mode, if `delay` time has been exceeded, execute
- // `callback`.
- exec();
-
- } else if ( no_trailing !== true ) {
- // In trailing throttle mode, since `delay` time has not been
- // exceeded, schedule `callback` to execute `delay` ms after most
- // recent execution.
- //
- // If `debounce_mode` is true (at_begin), schedule `clear` to execute
- // after `delay` ms.
- //
- // If `debounce_mode` is false (at end), schedule `callback` to
- // execute after `delay` ms.
- timeout_id = setTimeout( debounce_mode ? clear : exec, debounce_mode === undefined ? delay - elapsed : delay );
- }
- };
-
- // Set the guid of `wrapper` function to the same of original callback, so
- // it can be removed in jQuery 1.4+ .unbind or .die by using the original
- // callback as a reference.
- if ( $.guid ) {
- wrapper.guid = callback.guid = callback.guid || $.guid++;
- }
-
- // Return the wrapper function.
- return wrapper;
- };
-
- // Method: jQuery.debounce
- //
- // Debounce execution of a function. Debouncing, unlike throttling,
- // guarantees that a function is only executed a single time, either at the
- // very beginning of a series of calls, or at the very end. If you want to
- // simply rate-limit execution of a function, see the <jQuery.throttle>
- // method.
- //
- // In this visualization, | is a debounced-function call and X is the actual
- // callback execution:
- //
- // > Debounced with `at_begin` specified as false or unspecified:
- // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
- // > X X
- // >
- // > Debounced with `at_begin` specified as true:
- // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
- // > X X
- //
- // Usage:
- //
- // > var debounced = jQuery.debounce( delay, [ at_begin, ] callback );
- // >
- // > jQuery('selector').bind( 'someevent', debounced );
- // > jQuery('selector').unbind( 'someevent', debounced );
- //
- // This also works in jQuery 1.4+:
- //
- // > jQuery('selector').bind( 'someevent', jQuery.debounce( delay, [ at_begin, ] callback ) );
- // > jQuery('selector').unbind( 'someevent', callback );
- //
- // Arguments:
- //
- // delay - (Number) A zero-or-greater delay in milliseconds. For event
- // callbacks, values around 100 or 250 (or even higher) are most useful.
- // at_begin - (Boolean) Optional, defaults to false. If at_begin is false or
- // unspecified, callback will only be executed `delay` milliseconds after
- // the last debounced-function call. If at_begin is true, callback will be
- // executed only at the first debounced-function call. (After the
- // throttled-function has not been called for `delay` milliseconds, the
- // internal counter is reset)
- // callback - (Function) A function to be executed after delay milliseconds.
- // The `this` context and all arguments are passed through, as-is, to
- // `callback` when the debounced-function is executed.
- //
- // Returns:
- //
- // (Function) A new, debounced, function.
-
- $.debounce = function( delay, at_begin, callback ) {
- return callback === undefined
- ? jq_throttle( delay, at_begin, false )
- : jq_throttle( delay, callback, at_begin !== false );
- };
-
-})(this);
+++ /dev/null
-/*!
- * jQuery Cookie Plugin v1.3.1
- * https://github.com/carhartl/jquery-cookie
- *
- * Copyright 2013 Klaus Hartl
- * Released under the MIT license
- */
-(function ($, document, undefined) {
-
- var pluses = /\+/g;
-
- function raw(s) {
- return s;
- }
-
- function decoded(s) {
- return unRfc2068(decodeURIComponent(s.replace(pluses, ' ')));
- }
-
- function unRfc2068(value) {
- if (value.indexOf('"') === 0) {
- // This is a quoted cookie as according to RFC2068, unescape
- value = value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
- }
- return value;
- }
-
- function fromJSON(value) {
- return config.json ? JSON.parse(value) : value;
- }
-
- var config = $.cookie = function (key, value, options) {
-
- // write
- if (value !== undefined) {
- options = $.extend({}, config.defaults, options);
-
- if (value === null) {
- options.expires = -1;
- }
-
- if (typeof options.expires === 'number') {
- var days = options.expires, t = options.expires = new Date();
- t.setDate(t.getDate() + days);
- }
-
- value = config.json ? JSON.stringify(value) : String(value);
-
- return (document.cookie = [
- encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value),
- options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
- options.path ? '; path=' + options.path : '',
- options.domain ? '; domain=' + options.domain : '',
- options.secure ? '; secure' : ''
- ].join(''));
- }
-
- // read
- var decode = config.raw ? raw : decoded;
- var cookies = document.cookie.split('; ');
- var result = key ? null : {};
- for (var i = 0, l = cookies.length; i < l; i++) {
- var parts = cookies[i].split('=');
- var name = decode(parts.shift());
- var cookie = decode(parts.join('='));
-
- if (key && key === name) {
- result = fromJSON(cookie);
- break;
- }
-
- if (!key) {
- result[name] = fromJSON(cookie);
- }
- }
-
- return result;
- };
-
- config.defaults = {};
-
- $.removeCookie = function (key, options) {
- if ($.cookie(key) !== null) {
- $.cookie(key, null, options);
- return true;
- }
- return false;
- };
-
-})(jQuery, document);
+++ /dev/null
-/*!
- * jQuery Form Plugin
- * version: 3.14 (30-JUL-2012)
- * @requires jQuery v1.3.2 or later
- *
- * Examples and documentation at: http://malsup.com/jquery/form/
- * Project repository: https://github.com/malsup/form
- * Dual licensed under the MIT and GPL licenses:
- * http://malsup.github.com/mit-license.txt
- * http://malsup.github.com/gpl-license-v2.txt
- */
-/*global ActiveXObject alert */
-;(function($) {
-"use strict";
-
-/*
- Usage Note:
- -----------
- Do not use both ajaxSubmit and ajaxForm on the same form. These
- functions are mutually exclusive. Use ajaxSubmit if you want
- to bind your own submit handler to the form. For example,
-
- $(document).ready(function() {
- $('#myForm').on('submit', function(e) {
- e.preventDefault(); // <-- important
- $(this).ajaxSubmit({
- target: '#output'
- });
- });
- });
-
- Use ajaxForm when you want the plugin to manage all the event binding
- for you. For example,
-
- $(document).ready(function() {
- $('#myForm').ajaxForm({
- target: '#output'
- });
- });
-
- You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
- form does not have to exist when you invoke ajaxForm:
-
- $('#myForm').ajaxForm({
- delegation: true,
- target: '#output'
- });
-
- When using ajaxForm, the ajaxSubmit function will be invoked for you
- at the appropriate time.
-*/
-
-/**
- * Feature detection
- */
-var feature = {};
-feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
-feature.formdata = window.FormData !== undefined;
-
-/**
- * ajaxSubmit() provides a mechanism for immediately submitting
- * an HTML form using AJAX.
- */
-$.fn.ajaxSubmit = function(options) {
- /*jshint scripturl:true */
-
- // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
- if (!this.length) {
- log('ajaxSubmit: skipping submit process - no element selected');
- return this;
- }
-
- var method, action, url, $form = this;
-
- if (typeof options == 'function') {
- options = { success: options };
- }
-
- method = this.attr('method');
- action = this.attr('action');
- url = (typeof action === 'string') ? $.trim(action) : '';
- url = url || window.location.href || '';
- if (url) {
- // clean url (don't include hash vaue)
- url = (url.match(/^([^#]+)/)||[])[1];
- }
-
- options = $.extend(true, {
- url: url,
- success: $.ajaxSettings.success,
- type: method || 'GET',
- iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
- }, options);
-
- // hook for manipulating the form data before it is extracted;
- // convenient for use with rich editors like tinyMCE or FCKEditor
- var veto = {};
- this.trigger('form-pre-serialize', [this, options, veto]);
- if (veto.veto) {
- log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
- return this;
- }
-
- // provide opportunity to alter form data before it is serialized
- if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
- log('ajaxSubmit: submit aborted via beforeSerialize callback');
- return this;
- }
-
- var traditional = options.traditional;
- if ( traditional === undefined ) {
- traditional = $.ajaxSettings.traditional;
- }
-
- var elements = [];
- var qx, a = this.formToArray(options.semantic, elements);
- if (options.data) {
- options.extraData = options.data;
- qx = $.param(options.data, traditional);
- }
-
- // give pre-submit callback an opportunity to abort the submit
- if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
- log('ajaxSubmit: submit aborted via beforeSubmit callback');
- return this;
- }
-
- // fire vetoable 'validate' event
- this.trigger('form-submit-validate', [a, this, options, veto]);
- if (veto.veto) {
- log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
- return this;
- }
-
- var q = $.param(a, traditional);
- if (qx) {
- q = ( q ? (q + '&' + qx) : qx );
- }
- if (options.type.toUpperCase() == 'GET') {
- options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
- options.data = null; // data is null for 'get'
- }
- else {
- options.data = q; // data is the query string for 'post'
- }
-
- var callbacks = [];
- if (options.resetForm) {
- callbacks.push(function() { $form.resetForm(); });
- }
- if (options.clearForm) {
- callbacks.push(function() { $form.clearForm(options.includeHidden); });
- }
-
- // perform a load on the target only if dataType is not provided
- if (!options.dataType && options.target) {
- var oldSuccess = options.success || function(){};
- callbacks.push(function(data) {
- var fn = options.replaceTarget ? 'replaceWith' : 'html';
- $(options.target)[fn](data).each(oldSuccess, arguments);
- });
- }
- else if (options.success) {
- callbacks.push(options.success);
- }
-
- options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
- var context = options.context || this ; // jQuery 1.4+ supports scope context
- for (var i=0, max=callbacks.length; i < max; i++) {
- callbacks[i].apply(context, [data, status, xhr || $form, $form]);
- }
- };
-
- // are there files to upload?
- var fileInputs = $('input:file:enabled[value]', this); // [value] (issue #113)
- var hasFileInputs = fileInputs.length > 0;
- var mp = 'multipart/form-data';
- var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
-
- var fileAPI = feature.fileapi && feature.formdata;
- log("fileAPI :" + fileAPI);
- var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
-
- // options.iframe allows user to force iframe mode
- // 06-NOV-09: now defaulting to iframe mode if file input is detected
- if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
- // hack to fix Safari hang (thanks to Tim Molendijk for this)
- // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
- if (options.closeKeepAlive) {
- $.get(options.closeKeepAlive, function() {
- fileUploadIframe(a);
- });
- }
- else {
- fileUploadIframe(a);
- }
- }
- else if ((hasFileInputs || multipart) && fileAPI) {
- fileUploadXhr(a);
- }
- else {
- $.ajax(options);
- }
-
- // clear element array
- for (var k=0; k < elements.length; k++)
- elements[k] = null;
-
- // fire 'notify' event
- this.trigger('form-submit-notify', [this, options]);
- return this;
-
- // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
- function fileUploadXhr(a) {
- var formdata = new FormData();
-
- for (var i=0; i < a.length; i++) {
- formdata.append(a[i].name, a[i].value);
- }
-
- if (options.extraData) {
- for (var p in options.extraData)
- if (options.extraData.hasOwnProperty(p))
- formdata.append(p, options.extraData[p]);
- }
-
- options.data = null;
-
- var s = $.extend(true, {}, $.ajaxSettings, options, {
- contentType: false,
- processData: false,
- cache: false,
- type: 'POST'
- });
-
- if (options.uploadProgress) {
- // workaround because jqXHR does not expose upload property
- s.xhr = function() {
- var xhr = jQuery.ajaxSettings.xhr();
- if (xhr.upload) {
- xhr.upload.onprogress = function(event) {
- var percent = 0;
- var position = event.loaded || event.position; /*event.position is deprecated*/
- var total = event.total;
- if (event.lengthComputable) {
- percent = Math.ceil(position / total * 100);
- }
- options.uploadProgress(event, position, total, percent);
- };
- }
- return xhr;
- };
- }
-
- s.data = null;
- var beforeSend = s.beforeSend;
- s.beforeSend = function(xhr, o) {
- o.data = formdata;
- if(beforeSend)
- beforeSend.call(this, xhr, o);
- };
- $.ajax(s);
- }
-
- // private function for handling file uploads (hat tip to YAHOO!)
- function fileUploadIframe(a) {
- var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
- var useProp = !!$.fn.prop;
-
- if ($(':input[name=submit],:input[id=submit]', form).length) {
- // if there is an input with a name or id of 'submit' then we won't be
- // able to invoke the submit fn on the form (at least not x-browser)
- alert('Error: Form elements must not have name or id of "submit".');
- return;
- }
-
- if (a) {
- // ensure that every serialized input is still enabled
- for (i=0; i < elements.length; i++) {
- el = $(elements[i]);
- if ( useProp )
- el.prop('disabled', false);
- else
- el.removeAttr('disabled');
- }
- }
-
- s = $.extend(true, {}, $.ajaxSettings, options);
- s.context = s.context || s;
- id = 'jqFormIO' + (new Date().getTime());
- if (s.iframeTarget) {
- $io = $(s.iframeTarget);
- n = $io.attr('name');
- if (!n)
- $io.attr('name', id);
- else
- id = n;
- }
- else {
- $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
- $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
- }
- io = $io[0];
-
-
- xhr = { // mock object
- aborted: 0,
- responseText: null,
- responseXML: null,
- status: 0,
- statusText: 'n/a',
- getAllResponseHeaders: function() {},
- getResponseHeader: function() {},
- setRequestHeader: function() {},
- abort: function(status) {
- var e = (status === 'timeout' ? 'timeout' : 'aborted');
- log('aborting upload... ' + e);
- this.aborted = 1;
- // #214
- if (io.contentWindow.document.execCommand) {
- try { // #214
- io.contentWindow.document.execCommand('Stop');
- } catch(ignore) {}
- }
- $io.attr('src', s.iframeSrc); // abort op in progress
- xhr.error = e;
- if (s.error)
- s.error.call(s.context, xhr, e, status);
- if (g)
- $.event.trigger("ajaxError", [xhr, s, e]);
- if (s.complete)
- s.complete.call(s.context, xhr, e);
- }
- };
-
- g = s.global;
- // trigger ajax global events so that activity/block indicators work like normal
- if (g && 0 === $.active++) {
- $.event.trigger("ajaxStart");
- }
- if (g) {
- $.event.trigger("ajaxSend", [xhr, s]);
- }
-
- if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
- if (s.global) {
- $.active--;
- }
- return;
- }
- if (xhr.aborted) {
- return;
- }
-
- // add submitting element to data if we know it
- sub = form.clk;
- if (sub) {
- n = sub.name;
- if (n && !sub.disabled) {
- s.extraData = s.extraData || {};
- s.extraData[n] = sub.value;
- if (sub.type == "image") {
- s.extraData[n+'.x'] = form.clk_x;
- s.extraData[n+'.y'] = form.clk_y;
- }
- }
- }
-
- var CLIENT_TIMEOUT_ABORT = 1;
- var SERVER_ABORT = 2;
-
- function getDoc(frame) {
- var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
- return doc;
- }
-
- // Rails CSRF hack (thanks to Yvan Barthelemy)
- var csrf_token = $('meta[name=csrf-token]').attr('content');
- var csrf_param = $('meta[name=csrf-param]').attr('content');
- if (csrf_param && csrf_token) {
- s.extraData = s.extraData || {};
- s.extraData[csrf_param] = csrf_token;
- }
-
- // take a breath so that pending repaints get some cpu time before the upload starts
- function doSubmit() {
- // make sure form attrs are set
- var t = $form.attr('target'), a = $form.attr('action');
-
- // update form attrs in IE friendly way
- form.setAttribute('target',id);
- if (!method) {
- form.setAttribute('method', 'POST');
- }
- if (a != s.url) {
- form.setAttribute('action', s.url);
- }
-
- // ie borks in some cases when setting encoding
- if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
- $form.attr({
- encoding: 'multipart/form-data',
- enctype: 'multipart/form-data'
- });
- }
-
- // support timout
- if (s.timeout) {
- timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
- }
-
- // look for server aborts
- function checkState() {
- try {
- var state = getDoc(io).readyState;
- log('state = ' + state);
- if (state && state.toLowerCase() == 'uninitialized')
- setTimeout(checkState,50);
- }
- catch(e) {
- log('Server abort: ' , e, ' (', e.name, ')');
- cb(SERVER_ABORT);
- if (timeoutHandle)
- clearTimeout(timeoutHandle);
- timeoutHandle = undefined;
- }
- }
-
- // add "extra" data to form if provided in options
- var extraInputs = [];
- try {
- if (s.extraData) {
- for (var n in s.extraData) {
- if (s.extraData.hasOwnProperty(n)) {
- // if using the $.param format that allows for multiple values with the same name
- if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
- extraInputs.push(
- $('<input type="hidden" name="'+s.extraData[n].name+'">').attr('value',s.extraData[n].value)
- .appendTo(form)[0]);
- } else {
- extraInputs.push(
- $('<input type="hidden" name="'+n+'">').attr('value',s.extraData[n])
- .appendTo(form)[0]);
- }
- }
- }
- }
-
- if (!s.iframeTarget) {
- // add iframe to doc and submit the form
- $io.appendTo('body');
- if (io.attachEvent)
- io.attachEvent('onload', cb);
- else
- io.addEventListener('load', cb, false);
- }
- setTimeout(checkState,15);
- form.submit();
- }
- finally {
- // reset attrs and remove "extra" input elements
- form.setAttribute('action',a);
- if(t) {
- form.setAttribute('target', t);
- } else {
- $form.removeAttr('target');
- }
- $(extraInputs).remove();
- }
- }
-
- if (s.forceSync) {
- doSubmit();
- }
- else {
- setTimeout(doSubmit, 10); // this lets dom updates render
- }
-
- var data, doc, domCheckCount = 50, callbackProcessed;
-
- function cb(e) {
- if (xhr.aborted || callbackProcessed) {
- return;
- }
- try {
- doc = getDoc(io);
- }
- catch(ex) {
- log('cannot access response document: ', ex);
- e = SERVER_ABORT;
- }
- if (e === CLIENT_TIMEOUT_ABORT && xhr) {
- xhr.abort('timeout');
- return;
- }
- else if (e == SERVER_ABORT && xhr) {
- xhr.abort('server abort');
- return;
- }
-
- if (!doc || doc.location.href == s.iframeSrc) {
- // response not received yet
- if (!timedOut)
- return;
- }
- if (io.detachEvent)
- io.detachEvent('onload', cb);
- else
- io.removeEventListener('load', cb, false);
-
- var status = 'success', errMsg;
- try {
- if (timedOut) {
- throw 'timeout';
- }
-
- var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
- log('isXml='+isXml);
- if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
- if (--domCheckCount) {
- // in some browsers (Opera) the iframe DOM is not always traversable when
- // the onload callback fires, so we loop a bit to accommodate
- log('requeing onLoad callback, DOM not available');
- setTimeout(cb, 250);
- return;
- }
- // let this fall through because server response could be an empty document
- //log('Could not access iframe DOM after mutiple tries.');
- //throw 'DOMException: not available';
- }
-
- //log('response detected');
- var docRoot = doc.body ? doc.body : doc.documentElement;
- xhr.responseText = docRoot ? docRoot.innerHTML : null;
- xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
- if (isXml)
- s.dataType = 'xml';
- xhr.getResponseHeader = function(header){
- var headers = {'content-type': s.dataType};
- return headers[header];
- };
- // support for XHR 'status' & 'statusText' emulation :
- if (docRoot) {
- xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
- xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
- }
-
- var dt = (s.dataType || '').toLowerCase();
- var scr = /(json|script|text)/.test(dt);
- if (scr || s.textarea) {
- // see if user embedded response in textarea
- var ta = doc.getElementsByTagName('textarea')[0];
- if (ta) {
- xhr.responseText = ta.value;
- // support for XHR 'status' & 'statusText' emulation :
- xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
- xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
- }
- else if (scr) {
- // account for browsers injecting pre around json response
- var pre = doc.getElementsByTagName('pre')[0];
- var b = doc.getElementsByTagName('body')[0];
- if (pre) {
- xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
- }
- else if (b) {
- xhr.responseText = b.textContent ? b.textContent : b.innerText;
- }
- }
- }
- else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
- xhr.responseXML = toXml(xhr.responseText);
- }
-
- try {
- data = httpData(xhr, dt, s);
- }
- catch (e) {
- status = 'parsererror';
- xhr.error = errMsg = (e || status);
- }
- }
- catch (e) {
- log('error caught: ',e);
- status = 'error';
- xhr.error = errMsg = (e || status);
- }
-
- if (xhr.aborted) {
- log('upload aborted');
- status = null;
- }
-
- if (xhr.status) { // we've set xhr.status
- status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
- }
-
- // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
- if (status === 'success') {
- if (s.success)
- s.success.call(s.context, data, 'success', xhr);
- if (g)
- $.event.trigger("ajaxSuccess", [xhr, s]);
- }
- else if (status) {
- if (errMsg === undefined)
- errMsg = xhr.statusText;
- if (s.error)
- s.error.call(s.context, xhr, status, errMsg);
- if (g)
- $.event.trigger("ajaxError", [xhr, s, errMsg]);
- }
-
- if (g)
- $.event.trigger("ajaxComplete", [xhr, s]);
-
- if (g && ! --$.active) {
- $.event.trigger("ajaxStop");
- }
-
- if (s.complete)
- s.complete.call(s.context, xhr, status);
-
- callbackProcessed = true;
- if (s.timeout)
- clearTimeout(timeoutHandle);
-
- // clean up
- setTimeout(function() {
- if (!s.iframeTarget)
- $io.remove();
- xhr.responseXML = null;
- }, 100);
- }
-
- var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
- if (window.ActiveXObject) {
- doc = new ActiveXObject('Microsoft.XMLDOM');
- doc.async = 'false';
- doc.loadXML(s);
- }
- else {
- doc = (new DOMParser()).parseFromString(s, 'text/xml');
- }
- return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
- };
- var parseJSON = $.parseJSON || function(s) {
- /*jslint evil:true */
- return window['eval']('(' + s + ')');
- };
-
- var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
-
- var ct = xhr.getResponseHeader('content-type') || '',
- xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
- data = xml ? xhr.responseXML : xhr.responseText;
-
- if (xml && data.documentElement.nodeName === 'parsererror') {
- if ($.error)
- $.error('parsererror');
- }
- if (s && s.dataFilter) {
- data = s.dataFilter(data, type);
- }
- if (typeof data === 'string') {
- if (type === 'json' || !type && ct.indexOf('json') >= 0) {
- data = parseJSON(data);
- } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
- $.globalEval(data);
- }
- }
- return data;
- };
- }
-};
-
-/**
- * ajaxForm() provides a mechanism for fully automating form submission.
- *
- * The advantages of using this method instead of ajaxSubmit() are:
- *
- * 1: This method will include coordinates for <input type="image" /> elements (if the element
- * is used to submit the form).
- * 2. This method will include the submit element's name/value data (for the element that was
- * used to submit the form).
- * 3. This method binds the submit() method to the form for you.
- *
- * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
- * passes the options argument along after properly binding events for submit elements and
- * the form itself.
- */
-$.fn.ajaxForm = function(options) {
- options = options || {};
- options.delegation = options.delegation && $.isFunction($.fn.on);
-
- // in jQuery 1.3+ we can fix mistakes with the ready state
- if (!options.delegation && this.length === 0) {
- var o = { s: this.selector, c: this.context };
- if (!$.isReady && o.s) {
- log('DOM not ready, queuing ajaxForm');
- $(function() {
- $(o.s,o.c).ajaxForm(options);
- });
- return this;
- }
- // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
- log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
- return this;
- }
-
- if ( options.delegation ) {
- $(document)
- .off('submit.form-plugin', this.selector, doAjaxSubmit)
- .off('click.form-plugin', this.selector, captureSubmittingElement)
- .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
- .on('click.form-plugin', this.selector, options, captureSubmittingElement);
- return this;
- }
-
- return this.ajaxFormUnbind()
- .bind('submit.form-plugin', options, doAjaxSubmit)
- .bind('click.form-plugin', options, captureSubmittingElement);
-};
-
-// private event handlers
-function doAjaxSubmit(e) {
- /*jshint validthis:true */
- var options = e.data;
- if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
- e.preventDefault();
- $(this).ajaxSubmit(options);
- }
-}
-
-function captureSubmittingElement(e) {
- /*jshint validthis:true */
- var target = e.target;
- var $el = $(target);
- if (!($el.is(":submit,input:image"))) {
- // is this a child element of the submit el? (ex: a span within a button)
- var t = $el.closest(':submit');
- if (t.length === 0) {
- return;
- }
- target = t[0];
- }
- var form = this;
- form.clk = target;
- if (target.type == 'image') {
- if (e.offsetX !== undefined) {
- form.clk_x = e.offsetX;
- form.clk_y = e.offsetY;
- } else if (typeof $.fn.offset == 'function') {
- var offset = $el.offset();
- form.clk_x = e.pageX - offset.left;
- form.clk_y = e.pageY - offset.top;
- } else {
- form.clk_x = e.pageX - target.offsetLeft;
- form.clk_y = e.pageY - target.offsetTop;
- }
- }
- // clear form vars
- setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
-}
-
-
-// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
-$.fn.ajaxFormUnbind = function() {
- return this.unbind('submit.form-plugin click.form-plugin');
-};
-
-/**
- * formToArray() gathers form element data into an array of objects that can
- * be passed to any of the following ajax functions: $.get, $.post, or load.
- * Each object in the array has both a 'name' and 'value' property. An example of
- * an array for a simple login form might be:
- *
- * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
- *
- * It is this array that is passed to pre-submit callback functions provided to the
- * ajaxSubmit() and ajaxForm() methods.
- */
-$.fn.formToArray = function(semantic, elements) {
- var a = [];
- if (this.length === 0) {
- return a;
- }
-
- var form = this[0];
- var els = semantic ? form.getElementsByTagName('*') : form.elements;
- if (!els) {
- return a;
- }
-
- var i,j,n,v,el,max,jmax;
- for(i=0, max=els.length; i < max; i++) {
- el = els[i];
- n = el.name;
- if (!n) {
- continue;
- }
-
- if (semantic && form.clk && el.type == "image") {
- // handle image inputs on the fly when semantic == true
- if(!el.disabled && form.clk == el) {
- a.push({name: n, value: $(el).val(), type: el.type });
- a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
- }
- continue;
- }
-
- v = $.fieldValue(el, true);
- if (v && v.constructor == Array) {
- if (elements)
- elements.push(el);
- for(j=0, jmax=v.length; j < jmax; j++) {
- a.push({name: n, value: v[j]});
- }
- }
- else if (feature.fileapi && el.type == 'file' && !el.disabled) {
- if (elements)
- elements.push(el);
- var files = el.files;
- if (files.length) {
- for (j=0; j < files.length; j++) {
- a.push({name: n, value: files[j], type: el.type});
- }
- }
- else {
- // #180
- a.push({ name: n, value: '', type: el.type });
- }
- }
- else if (v !== null && typeof v != 'undefined') {
- if (elements)
- elements.push(el);
- a.push({name: n, value: v, type: el.type, required: el.required});
- }
- }
-
- if (!semantic && form.clk) {
- // input type=='image' are not found in elements array! handle it here
- var $input = $(form.clk), input = $input[0];
- n = input.name;
- if (n && !input.disabled && input.type == 'image') {
- a.push({name: n, value: $input.val()});
- a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
- }
- }
- return a;
-};
-
-/**
- * Serializes form data into a 'submittable' string. This method will return a string
- * in the format: name1=value1&name2=value2
- */
-$.fn.formSerialize = function(semantic) {
- //hand off to jQuery.param for proper encoding
- return $.param(this.formToArray(semantic));
-};
-
-/**
- * Serializes all field elements in the jQuery object into a query string.
- * This method will return a string in the format: name1=value1&name2=value2
- */
-$.fn.fieldSerialize = function(successful) {
- var a = [];
- this.each(function() {
- var n = this.name;
- if (!n) {
- return;
- }
- var v = $.fieldValue(this, successful);
- if (v && v.constructor == Array) {
- for (var i=0,max=v.length; i < max; i++) {
- a.push({name: n, value: v[i]});
- }
- }
- else if (v !== null && typeof v != 'undefined') {
- a.push({name: this.name, value: v});
- }
- });
- //hand off to jQuery.param for proper encoding
- return $.param(a);
-};
-
-/**
- * Returns the value(s) of the element in the matched set. For example, consider the following form:
- *
- * <form><fieldset>
- * <input name="A" type="text" />
- * <input name="A" type="text" />
- * <input name="B" type="checkbox" value="B1" />
- * <input name="B" type="checkbox" value="B2"/>
- * <input name="C" type="radio" value="C1" />
- * <input name="C" type="radio" value="C2" />
- * </fieldset></form>
- *
- * var v = $(':text').fieldValue();
- * // if no values are entered into the text inputs
- * v == ['','']
- * // if values entered into the text inputs are 'foo' and 'bar'
- * v == ['foo','bar']
- *
- * var v = $(':checkbox').fieldValue();
- * // if neither checkbox is checked
- * v === undefined
- * // if both checkboxes are checked
- * v == ['B1', 'B2']
- *
- * var v = $(':radio').fieldValue();
- * // if neither radio is checked
- * v === undefined
- * // if first radio is checked
- * v == ['C1']
- *
- * The successful argument controls whether or not the field element must be 'successful'
- * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
- * The default value of the successful argument is true. If this value is false the value(s)
- * for each element is returned.
- *
- * Note: This method *always* returns an array. If no valid value can be determined the
- * array will be empty, otherwise it will contain one or more values.
- */
-$.fn.fieldValue = function(successful) {
- for (var val=[], i=0, max=this.length; i < max; i++) {
- var el = this[i];
- var v = $.fieldValue(el, successful);
- if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
- continue;
- }
- if (v.constructor == Array)
- $.merge(val, v);
- else
- val.push(v);
- }
- return val;
-};
-
-/**
- * Returns the value of the field element.
- */
-$.fieldValue = function(el, successful) {
- var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
- if (successful === undefined) {
- successful = true;
- }
-
- if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
- (t == 'checkbox' || t == 'radio') && !el.checked ||
- (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
- tag == 'select' && el.selectedIndex == -1)) {
- return null;
- }
-
- if (tag == 'select') {
- var index = el.selectedIndex;
- if (index < 0) {
- return null;
- }
- var a = [], ops = el.options;
- var one = (t == 'select-one');
- var max = (one ? index+1 : ops.length);
- for(var i=(one ? index : 0); i < max; i++) {
- var op = ops[i];
- if (op.selected) {
- var v = op.value;
- if (!v) { // extra pain for IE...
- v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
- }
- if (one) {
- return v;
- }
- a.push(v);
- }
- }
- return a;
- }
- return $(el).val();
-};
-
-/**
- * Clears the form data. Takes the following actions on the form's input fields:
- * - input text fields will have their 'value' property set to the empty string
- * - select elements will have their 'selectedIndex' property set to -1
- * - checkbox and radio inputs will have their 'checked' property set to false
- * - inputs of type submit, button, reset, and hidden will *not* be effected
- * - button elements will *not* be effected
- */
-$.fn.clearForm = function(includeHidden) {
- return this.each(function() {
- $('input,select,textarea', this).clearFields(includeHidden);
- });
-};
-
-/**
- * Clears the selected form elements.
- */
-$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
- var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
- return this.each(function() {
- var t = this.type, tag = this.tagName.toLowerCase();
- if (re.test(t) || tag == 'textarea') {
- this.value = '';
- }
- else if (t == 'checkbox' || t == 'radio') {
- this.checked = false;
- }
- else if (tag == 'select') {
- this.selectedIndex = -1;
- }
- else if (includeHidden) {
- // includeHidden can be the value true, or it can be a selector string
- // indicating a special test; for example:
- // $('#myForm').clearForm('.special:hidden')
- // the above would clean hidden inputs that have the class of 'special'
- if ( (includeHidden === true && /hidden/.test(t)) ||
- (typeof includeHidden == 'string' && $(this).is(includeHidden)) )
- this.value = '';
- }
- });
-};
-
-/**
- * Resets the form data. Causes all form elements to be reset to their original value.
- */
-$.fn.resetForm = function() {
- return this.each(function() {
- // guard against an input with the name of 'reset'
- // note that IE reports the reset function as an 'object'
- if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
- this.reset();
- }
- });
-};
-
-/**
- * Enables or disables any matching elements.
- */
-$.fn.enable = function(b) {
- if (b === undefined) {
- b = true;
- }
- return this.each(function() {
- this.disabled = !b;
- });
-};
-
-/**
- * Checks/unchecks any matching checkboxes or radio buttons and
- * selects/deselects and matching option elements.
- */
-$.fn.selected = function(select) {
- if (select === undefined) {
- select = true;
- }
- return this.each(function() {
- var t = this.type;
- if (t == 'checkbox' || t == 'radio') {
- this.checked = select;
- }
- else if (this.tagName.toLowerCase() == 'option') {
- var $sel = $(this).parent('select');
- if (select && $sel[0] && $sel[0].type == 'select-one') {
- // deselect all other options
- $sel.find('option').selected(false);
- }
- this.selected = select;
- }
- });
-};
-
-// expose debug var
-$.fn.ajaxSubmit.debug = false;
-
-// helper fn for console logging
-function log() {
- if (!$.fn.ajaxSubmit.debug)
- return;
- var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
- if (window.console && window.console.log) {
- window.console.log(msg);
- }
- else if (window.opera && window.opera.postError) {
- window.opera.postError(msg);
- }
-}
-
-})(jQuery);
+++ /dev/null
-/**
- * jQuery fullscreen plugin v2.0.0-git (9f8f97d127)
- * https://github.com/theopolisme/jquery-fullscreen
- *
- * Copyright (c) 2013 Theopolisme <theopolismewiki@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-( function ( $ ) {
- var setupFullscreen,
- fsClass = 'jq-fullscreened';
-
- /**
- * On fullscreenchange, trigger a jq-fullscreen-change event
- * The event is given an object, which contains the fullscreened DOM element (element), if any
- * and a boolean value (fullscreen) indicating if we've entered or exited fullscreen mode
- * Also remove the 'fullscreened' class from elements that are no longer fullscreen
- */
- function handleFullscreenChange () {
- var fullscreenElement = document.fullscreenElement ||
- document.mozFullScreenElement ||
- document.webkitFullscreenElement ||
- document.msFullscreenElement;
-
- if ( !fullscreenElement ) {
- $( '.' + fsClass ).data( 'isFullscreened', false ).removeClass( fsClass );
- }
-
- $( document ).trigger( $.Event( 'jq-fullscreen-change', { element: fullscreenElement, fullscreen: !!fullscreenElement } ) );
- }
-
- /**
- * Enters full screen with the "this" element in focus.
- * Check the .data( 'isFullscreened' ) of the return value to check
- * success or failure, if you're into that sort of thing.
- * @chainable
- * @return {jQuery}
- */
- function enterFullscreen () {
- var element = this.get(0),
- $element = this.first();
- if ( element ) {
- if ( element.requestFullscreen ) {
- element.requestFullscreen();
- } else if ( element.mozRequestFullScreen ) {
- element.mozRequestFullScreen();
- } else if ( element.webkitRequestFullscreen ) {
- element.webkitRequestFullscreen();
- } else if ( element.msRequestFullscreen ) {
- element.msRequestFullscreen();
- } else {
- // Unable to make fullscreen
- $element.data( 'isFullscreened', false );
- return this;
- }
- // Add the fullscreen class and data attribute to `element`
- $element.addClass( fsClass ).data( 'isFullscreened', true );
- return this;
- } else {
- $element.data( 'isFullscreened', false );
- return this;
- }
- }
-
- /**
- * Brings the "this" element out of fullscreen.
- * Check the .data( 'isFullscreened' ) of the return value to check
- * success or failure, if you're into that sort of thing.
- * @chainable
- * @return {jQuery}
- */
- function exitFullscreen () {
- var fullscreenElement = ( document.fullscreenElement ||
- document.mozFullScreenElement ||
- document.webkitFullscreenElement ||
- document.msFullscreenElement );
-
- // Ensure that we only exit fullscreen if exitFullscreen() is being called on the same element that is currently fullscreen
- if ( fullscreenElement && this.get(0) === fullscreenElement ) {
- if ( document.exitFullscreen ) {
- document.exitFullscreen();
- } else if ( document.mozCancelFullScreen ) {
- document.mozCancelFullScreen();
- } else if ( document.webkitCancelFullScreen ) {
- document.webkitCancelFullScreen();
- } else if ( document.msExitFullscreen ) {
- document.msExitFullscreen();
- } else {
- // Unable to cancel fullscreen mode
- return this;
- }
- // We don't need to remove the fullscreen class here,
- // because it will be removed in handleFullscreenChange.
- // But we should change the data on the element so the
- // caller can check for success.
- this.first().data( 'isFullscreened', false );
- }
-
- return this;
- }
-
- /**
- * Set up fullscreen handling and install necessary event handlers.
- * Return false if fullscreen is not supported.
- */
- setupFullscreen = function () {
- if ( $.support.fullscreen ) {
- // When the fullscreen mode is changed, trigger the
- // fullscreen events (and when exiting,
- // also remove the fullscreen class)
- $( document ).on( 'fullscreenchange webkitfullscreenchange mozfullscreenchange MSFullscreenChange', handleFullscreenChange);
- // Convenience wrapper so that one only needs to listen for
- // 'fullscreenerror', not all of the prefixed versions
- $( document ).on( 'webkitfullscreenerror mozfullscreenerror MSFullscreenError', function () {
- $( document ).trigger( $.Event( 'fullscreenerror' ) );
- } );
- // Fullscreen has been set up, so always return true
- setupFullscreen = function () { return true; };
- return true;
- } else {
- // Always return false from now on, since fullscreen is not supported
- setupFullscreen = function () { return false; };
- return false;
- }
- };
-
- /**
- * Set up fullscreen handling if necessary, then make the first element
- * matching the given selector fullscreen
- * @chainable
- * @return {jQuery}
- */
- $.fn.enterFullscreen = function () {
- if ( setupFullscreen() ) {
- $.fn.enterFullscreen = enterFullscreen;
- return this.enterFullscreen();
- } else {
- $.fn.enterFullscreen = function () { return this; };
- return this;
- }
- };
-
- /**
- * Set up fullscreen handling if necessary, then cancel fullscreen mode
- * for the first element matching the given selector.
- * @chainable
- * @return {jQuery}
- */
- $.fn.exitFullscreen = function () {
- if ( setupFullscreen() ) {
- $.fn.exitFullscreen = exitFullscreen;
- return this.exitFullscreen();
- } else {
- $.fn.exitFullscreen = function () { return this; };
- return this;
- }
- };
-
- $.support.fullscreen = document.fullscreenEnabled ||
- document.webkitFullscreenEnabled ||
- document.mozFullScreenEnabled ||
- document.msFullscreenEnabled;
-}( jQuery ) );
+++ /dev/null
-/**
-* hoverIntent is similar to jQuery's built-in "hover" function except that
-* instead of firing the onMouseOver event immediately, hoverIntent checks
-* to see if the user's mouse has slowed down (beneath the sensitivity
-* threshold) before firing the onMouseOver event.
-*
-* hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+
-* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
-*
-* hoverIntent is currently available for use in all personal or commercial
-* projects under both MIT and GPL licenses. This means that you can choose
-* the license that best suits your project, and use it accordingly.
-*
-* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
-* $("ul li").hoverIntent( showNav , hideNav );
-*
-* // advanced usage receives configuration object only
-* $("ul li").hoverIntent({
-* sensitivity: 7, // number = sensitivity threshold (must be 1 or higher)
-* interval: 100, // number = milliseconds of polling interval
-* over: showNav, // function = onMouseOver callback (required)
-* timeout: 0, // number = milliseconds delay before onMouseOut function call
-* out: hideNav // function = onMouseOut callback (required)
-* });
-*
-* @param f onMouseOver function || An object with configuration options
-* @param g onMouseOut function || Nothing (use configuration options object)
-* @author Brian Cherne <brian@cherne.net>
-*/
-(function($) {
- $.fn.hoverIntent = function(f,g) {
- // default configuration options
- var cfg = {
- sensitivity: 7,
- interval: 100,
- timeout: 0
- };
- // override configuration options with user supplied object
- cfg = $.extend(cfg, g ? { over: f, out: g } : f );
-
- // instantiate variables
- // cX, cY = current X and Y position of mouse, updated by mousemove event
- // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
- var cX, cY, pX, pY;
-
- // A private function for getting mouse position
- var track = function(ev) {
- cX = ev.pageX;
- cY = ev.pageY;
- };
-
- // A private function for comparing current and previous mouse position
- var compare = function(ev,ob) {
- ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
- // compare mouse positions to see if they've crossed the threshold
- if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
- $(ob).unbind("mousemove",track);
- // set hoverIntent state to true (so mouseOut can be called)
- ob.hoverIntent_s = 1;
- return cfg.over.apply(ob,[ev]);
- } else {
- // set previous coordinates for next time
- pX = cX; pY = cY;
- // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
- ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
- }
- };
-
- // A private function for delaying the mouseOut function
- var delay = function(ev,ob) {
- ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
- ob.hoverIntent_s = 0;
- return cfg.out.apply(ob,[ev]);
- };
-
- // A private function for handling mouse 'hovering'
- var handleHover = function(e) {
- // next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut
- var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
- while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } }
- if ( p == this ) { return false; }
-
- // copy objects to be passed into t (required for event object to be passed in IE)
- var ev = $.extend({},e);
- var ob = this;
-
- // cancel hoverIntent timer if it exists
- if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
-
- // else e.type == "onmouseover"
- if (e.type == "mouseover") {
- // set "previous" X and Y position based on initial entry point
- pX = ev.pageX; pY = ev.pageY;
- // update "current" X and Y position based on mousemove
- $(ob).bind("mousemove",track);
- // start polling interval (self-calling timeout) to compare mouse coordinates over time
- if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
-
- // else e.type == "onmouseout"
- } else {
- // unbind expensive mousemove event
- $(ob).unbind("mousemove",track);
- // if hoverIntent state is true, then call the mouseOut function after the specified delay
- if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
- }
- };
-
- // bind the function to the two event listeners
- return this.mouseover(handleHover).mouseout(handleHover);
- };
-})(jQuery);
\ No newline at end of file
+++ /dev/null
-/*
- * ----------------------------- JSTORAGE -------------------------------------
- * Simple local storage wrapper to save data on the browser side, supporting
- * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
- *
- * Author: Andris Reinman, andris.reinman@gmail.com
- * Project homepage: www.jstorage.info
- *
- * Licensed under Unlicense:
- *
- * This is free and unencumbered software released into the public domain.
- *
- * Anyone is free to copy, modify, publish, use, compile, sell, or
- * distribute this software, either in source code form or as a compiled
- * binary, for any purpose, commercial or non-commercial, and by any
- * means.
- *
- * In jurisdictions that recognize copyright laws, the author or authors
- * of this software dedicate any and all copyright interest in the
- * software to the public domain. We make this dedication for the benefit
- * of the public at large and to the detriment of our heirs and
- * successors. We intend this dedication to be an overt act of
- * relinquishment in perpetuity of all present and future rights to this
- * software under copyright law.
- *
- * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
- * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
- * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- *
- * For more information, please refer to <http://unlicense.org/>
- */
-
-/* global ActiveXObject: false */
-/* jshint browser: true */
-
-(function() {
- 'use strict';
-
- var
- /* jStorage version */
- JSTORAGE_VERSION = '0.4.12',
-
- /* detect a dollar object or create one if not found */
- $ = window.jQuery || window.$ || (window.$ = {}),
-
- /* check for a JSON handling support */
- JSON = {
- parse: window.JSON && (window.JSON.parse || window.JSON.decode) ||
- String.prototype.evalJSON && function(str) {
- return String(str).evalJSON();
- } ||
- $.parseJSON ||
- $.evalJSON,
- stringify: Object.toJSON ||
- window.JSON && (window.JSON.stringify || window.JSON.encode) ||
- $.toJSON
- };
-
- // Break if no JSON support was found
- if (typeof JSON.parse !== 'function' || typeof JSON.stringify !== 'function') {
- throw new Error('No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page');
- }
-
- var
- /* This is the object, that holds the cached values */
- _storage = {
- __jstorage_meta: {
- CRC32: {}
- }
- },
-
- /* Actual browser storage (localStorage or globalStorage['domain']) */
- _storage_service = {
- jStorage: '{}'
- },
-
- /* DOM element for older IE versions, holds userData behavior */
- _storage_elm = null,
-
- /* How much space does the storage take */
- _storage_size = 0,
-
- /* which backend is currently used */
- _backend = false,
-
- /* onchange observers */
- _observers = {},
-
- /* timeout to wait after onchange event */
- _observer_timeout = false,
-
- /* last update time */
- _observer_update = 0,
-
- /* pubsub observers */
- _pubsub_observers = {},
-
- /* skip published items older than current timestamp */
- _pubsub_last = +new Date(),
-
- /* Next check for TTL */
- _ttl_timeout,
-
- /**
- * XML encoding and decoding as XML nodes can't be JSON'ized
- * XML nodes are encoded and decoded if the node is the value to be saved
- * but not if it's as a property of another object
- * Eg. -
- * $.jStorage.set('key', xmlNode); // IS OK
- * $.jStorage.set('key', {xml: xmlNode}); // NOT OK
- */
- _XMLService = {
-
- /**
- * Validates a XML node to be XML
- * based on jQuery.isXML function
- */
- isXML: function(elm) {
- var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
- return documentElement ? documentElement.nodeName !== 'HTML' : false;
- },
-
- /**
- * Encodes a XML node to string
- * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
- */
- encode: function(xmlNode) {
- if (!this.isXML(xmlNode)) {
- return false;
- }
- try { // Mozilla, Webkit, Opera
- return new XMLSerializer().serializeToString(xmlNode);
- } catch (E1) {
- try { // IE
- return xmlNode.xml;
- } catch (E2) {}
- }
- return false;
- },
-
- /**
- * Decodes a XML node from string
- * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
- */
- decode: function(xmlString) {
- var dom_parser = ('DOMParser' in window && (new DOMParser()).parseFromString) ||
- (window.ActiveXObject && function(_xmlString) {
- var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
- xml_doc.async = 'false';
- xml_doc.loadXML(_xmlString);
- return xml_doc;
- }),
- resultXML;
- if (!dom_parser) {
- return false;
- }
- resultXML = dom_parser.call('DOMParser' in window && (new DOMParser()) || window, xmlString, 'text/xml');
- return this.isXML(resultXML) ? resultXML : false;
- }
- };
-
-
- ////////////////////////// PRIVATE METHODS ////////////////////////
-
- /**
- * Initialization function. Detects if the browser supports DOM Storage
- * or userData behavior and behaves accordingly.
- */
- function _init() {
- /* Check if browser supports localStorage */
- var localStorageReallyWorks = false;
- if ('localStorage' in window) {
- try {
- window.localStorage.setItem('_tmptest', 'tmpval');
- localStorageReallyWorks = true;
- window.localStorage.removeItem('_tmptest');
- } catch (BogusQuotaExceededErrorOnIos5) {
- // Thanks be to iOS5 Private Browsing mode which throws
- // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
- }
- }
-
- if (localStorageReallyWorks) {
- try {
- if (window.localStorage) {
- _storage_service = window.localStorage;
- _backend = 'localStorage';
- _observer_update = _storage_service.jStorage_update;
- }
- } catch (E3) { /* Firefox fails when touching localStorage and cookies are disabled */ }
- }
- /* Check if browser supports globalStorage */
- else if ('globalStorage' in window) {
- try {
- if (window.globalStorage) {
- if (window.location.hostname == 'localhost') {
- _storage_service = window.globalStorage['localhost.localdomain'];
- } else {
- _storage_service = window.globalStorage[window.location.hostname];
- }
- _backend = 'globalStorage';
- _observer_update = _storage_service.jStorage_update;
- }
- } catch (E4) { /* Firefox fails when touching localStorage and cookies are disabled */ }
- }
- /* Check if browser supports userData behavior */
- else {
- _storage_elm = document.createElement('link');
- if (_storage_elm.addBehavior) {
-
- /* Use a DOM element to act as userData storage */
- _storage_elm.style.behavior = 'url(#default#userData)';
-
- /* userData element needs to be inserted into the DOM! */
- document.getElementsByTagName('head')[0].appendChild(_storage_elm);
-
- try {
- _storage_elm.load('jStorage');
- } catch (E) {
- // try to reset cache
- _storage_elm.setAttribute('jStorage', '{}');
- _storage_elm.save('jStorage');
- _storage_elm.load('jStorage');
- }
-
- var data = '{}';
- try {
- data = _storage_elm.getAttribute('jStorage');
- } catch (E5) {}
-
- try {
- _observer_update = _storage_elm.getAttribute('jStorage_update');
- } catch (E6) {}
-
- _storage_service.jStorage = data;
- _backend = 'userDataBehavior';
- } else {
- _storage_elm = null;
- return;
- }
- }
-
- // Load data from storage
- _load_storage();
-
- // remove dead keys
- _handleTTL();
-
- // start listening for changes
- _setupObserver();
-
- // initialize publish-subscribe service
- _handlePubSub();
-
- // handle cached navigation
- if ('addEventListener' in window) {
- window.addEventListener('pageshow', function(event) {
- if (event.persisted) {
- _storageObserver();
- }
- }, false);
- }
- }
-
- /**
- * Reload data from storage when needed
- */
- function _reloadData() {
- var data = '{}';
-
- if (_backend == 'userDataBehavior') {
- _storage_elm.load('jStorage');
-
- try {
- data = _storage_elm.getAttribute('jStorage');
- } catch (E5) {}
-
- try {
- _observer_update = _storage_elm.getAttribute('jStorage_update');
- } catch (E6) {}
-
- _storage_service.jStorage = data;
- }
-
- _load_storage();
-
- // remove dead keys
- _handleTTL();
-
- _handlePubSub();
- }
-
- /**
- * Sets up a storage change observer
- */
- function _setupObserver() {
- if (_backend == 'localStorage' || _backend == 'globalStorage') {
- if ('addEventListener' in window) {
- window.addEventListener('storage', _storageObserver, false);
- } else {
- document.attachEvent('onstorage', _storageObserver);
- }
- } else if (_backend == 'userDataBehavior') {
- setInterval(_storageObserver, 1000);
- }
- }
-
- /**
- * Fired on any kind of data change, needs to check if anything has
- * really been changed
- */
- function _storageObserver() {
- var updateTime;
- // cumulate change notifications with timeout
- clearTimeout(_observer_timeout);
- _observer_timeout = setTimeout(function() {
-
- if (_backend == 'localStorage' || _backend == 'globalStorage') {
- updateTime = _storage_service.jStorage_update;
- } else if (_backend == 'userDataBehavior') {
- _storage_elm.load('jStorage');
- try {
- updateTime = _storage_elm.getAttribute('jStorage_update');
- } catch (E5) {}
- }
-
- if (updateTime && updateTime != _observer_update) {
- _observer_update = updateTime;
- _checkUpdatedKeys();
- }
-
- }, 25);
- }
-
- /**
- * Reloads the data and checks if any keys are changed
- */
- function _checkUpdatedKeys() {
- var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
- newCrc32List;
-
- _reloadData();
- newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));
-
- var key,
- updated = [],
- removed = [];
-
- for (key in oldCrc32List) {
- if (oldCrc32List.hasOwnProperty(key)) {
- if (!newCrc32List[key]) {
- removed.push(key);
- continue;
- }
- if (oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0, 2) == '2.') {
- updated.push(key);
- }
- }
- }
-
- for (key in newCrc32List) {
- if (newCrc32List.hasOwnProperty(key)) {
- if (!oldCrc32List[key]) {
- updated.push(key);
- }
- }
- }
-
- _fireObservers(updated, 'updated');
- _fireObservers(removed, 'deleted');
- }
-
- /**
- * Fires observers for updated keys
- *
- * @param {Array|String} keys Array of key names or a key
- * @param {String} action What happened with the value (updated, deleted, flushed)
- */
- function _fireObservers(keys, action) {
- keys = [].concat(keys || []);
-
- var i, j, len, jlen;
-
- if (action == 'flushed') {
- keys = [];
- for (var key in _observers) {
- if (_observers.hasOwnProperty(key)) {
- keys.push(key);
- }
- }
- action = 'deleted';
- }
- for (i = 0, len = keys.length; i < len; i++) {
- if (_observers[keys[i]]) {
- for (j = 0, jlen = _observers[keys[i]].length; j < jlen; j++) {
- _observers[keys[i]][j](keys[i], action);
- }
- }
- if (_observers['*']) {
- for (j = 0, jlen = _observers['*'].length; j < jlen; j++) {
- _observers['*'][j](keys[i], action);
- }
- }
- }
- }
-
- /**
- * Publishes key change to listeners
- */
- function _publishChange() {
- var updateTime = (+new Date()).toString();
-
- if (_backend == 'localStorage' || _backend == 'globalStorage') {
- try {
- _storage_service.jStorage_update = updateTime;
- } catch (E8) {
- // safari private mode has been enabled after the jStorage initialization
- _backend = false;
- }
- } else if (_backend == 'userDataBehavior') {
- _storage_elm.setAttribute('jStorage_update', updateTime);
- _storage_elm.save('jStorage');
- }
-
- _storageObserver();
- }
-
- /**
- * Loads the data from the storage based on the supported mechanism
- */
- function _load_storage() {
- /* if jStorage string is retrieved, then decode it */
- if (_storage_service.jStorage) {
- try {
- _storage = JSON.parse(String(_storage_service.jStorage));
- } catch (E6) {
- _storage_service.jStorage = '{}';
- }
- } else {
- _storage_service.jStorage = '{}';
- }
- _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
-
- if (!_storage.__jstorage_meta) {
- _storage.__jstorage_meta = {};
- }
- if (!_storage.__jstorage_meta.CRC32) {
- _storage.__jstorage_meta.CRC32 = {};
- }
- }
-
- /**
- * This functions provides the 'save' mechanism to store the jStorage object
- */
- function _save() {
- _dropOldEvents(); // remove expired events
- try {
- _storage_service.jStorage = JSON.stringify(_storage);
- // If userData is used as the storage engine, additional
- if (_storage_elm) {
- _storage_elm.setAttribute('jStorage', _storage_service.jStorage);
- _storage_elm.save('jStorage');
- }
- _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
- } catch (E7) { /* probably cache is full, nothing is saved this way*/ }
- }
-
- /**
- * Function checks if a key is set and is string or numberic
- *
- * @param {String} key Key name
- */
- function _checkKey(key) {
- if (typeof key != 'string' && typeof key != 'number') {
- throw new TypeError('Key name must be string or numeric');
- }
- if (key == '__jstorage_meta') {
- throw new TypeError('Reserved key name');
- }
- return true;
- }
-
- /**
- * Removes expired keys
- */
- function _handleTTL() {
- var curtime, i, TTL, CRC32, nextExpire = Infinity,
- changed = false,
- deleted = [];
-
- clearTimeout(_ttl_timeout);
-
- if (!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != 'object') {
- // nothing to do here
- return;
- }
-
- curtime = +new Date();
- TTL = _storage.__jstorage_meta.TTL;
-
- CRC32 = _storage.__jstorage_meta.CRC32;
- for (i in TTL) {
- if (TTL.hasOwnProperty(i)) {
- if (TTL[i] <= curtime) {
- delete TTL[i];
- delete CRC32[i];
- delete _storage[i];
- changed = true;
- deleted.push(i);
- } else if (TTL[i] < nextExpire) {
- nextExpire = TTL[i];
- }
- }
- }
-
- // set next check
- if (nextExpire != Infinity) {
- _ttl_timeout = setTimeout(_handleTTL, Math.min(nextExpire - curtime, 0x7FFFFFFF));
- }
-
- // save changes
- if (changed) {
- _save();
- _publishChange();
- _fireObservers(deleted, 'deleted');
- }
- }
-
- /**
- * Checks if there's any events on hold to be fired to listeners
- */
- function _handlePubSub() {
- var i, len;
- if (!_storage.__jstorage_meta.PubSub) {
- return;
- }
- var pubelm,
- _pubsubCurrent = _pubsub_last,
- needFired = [];
-
- for (i = len = _storage.__jstorage_meta.PubSub.length - 1; i >= 0; i--) {
- pubelm = _storage.__jstorage_meta.PubSub[i];
- if (pubelm[0] > _pubsub_last) {
- _pubsubCurrent = pubelm[0];
- needFired.unshift(pubelm);
- }
- }
-
- for (i = needFired.length - 1; i >= 0; i--) {
- _fireSubscribers(needFired[i][1], needFired[i][2]);
- }
-
- _pubsub_last = _pubsubCurrent;
- }
-
- /**
- * Fires all subscriber listeners for a pubsub channel
- *
- * @param {String} channel Channel name
- * @param {Mixed} payload Payload data to deliver
- */
- function _fireSubscribers(channel, payload) {
- if (_pubsub_observers[channel]) {
- for (var i = 0, len = _pubsub_observers[channel].length; i < len; i++) {
- // send immutable data that can't be modified by listeners
- try {
- _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
- } catch (E) {}
- }
- }
- }
-
- /**
- * Remove old events from the publish stream (at least 2sec old)
- */
- function _dropOldEvents() {
- if (!_storage.__jstorage_meta.PubSub) {
- return;
- }
-
- var retire = +new Date() - 2000;
-
- for (var i = 0, len = _storage.__jstorage_meta.PubSub.length; i < len; i++) {
- if (_storage.__jstorage_meta.PubSub[i][0] <= retire) {
- // deleteCount is needed for IE6
- _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
- break;
- }
- }
-
- if (!_storage.__jstorage_meta.PubSub.length) {
- delete _storage.__jstorage_meta.PubSub;
- }
-
- }
-
- /**
- * Publish payload to a channel
- *
- * @param {String} channel Channel name
- * @param {Mixed} payload Payload to send to the subscribers
- */
- function _publish(channel, payload) {
- if (!_storage.__jstorage_meta) {
- _storage.__jstorage_meta = {};
- }
- if (!_storage.__jstorage_meta.PubSub) {
- _storage.__jstorage_meta.PubSub = [];
- }
-
- _storage.__jstorage_meta.PubSub.unshift([+new Date(), channel, payload]);
-
- _save();
- _publishChange();
- }
-
-
- /**
- * JS Implementation of MurmurHash2
- *
- * SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
- *
- * @author <a href='mailto:gary.court@gmail.com'>Gary Court</a>
- * @see http://github.com/garycourt/murmurhash-js
- * @author <a href='mailto:aappleby@gmail.com'>Austin Appleby</a>
- * @see http://sites.google.com/site/murmurhash/
- *
- * @param {string} str ASCII only
- * @param {number} seed Positive integer only
- * @return {number} 32-bit positive integer hash
- */
-
- function murmurhash2_32_gc(str, seed) {
- var
- l = str.length,
- h = seed ^ l,
- i = 0,
- k;
-
- while (l >= 4) {
- k =
- ((str.charCodeAt(i) & 0xff)) |
- ((str.charCodeAt(++i) & 0xff) << 8) |
- ((str.charCodeAt(++i) & 0xff) << 16) |
- ((str.charCodeAt(++i) & 0xff) << 24);
-
- k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
- k ^= k >>> 24;
- k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
-
- h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
-
- l -= 4;
- ++i;
- }
-
- switch (l) {
- case 3:
- h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
- /* falls through */
- case 2:
- h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
- /* falls through */
- case 1:
- h ^= (str.charCodeAt(i) & 0xff);
- h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
- }
-
- h ^= h >>> 13;
- h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
- h ^= h >>> 15;
-
- return h >>> 0;
- }
-
- ////////////////////////// PUBLIC INTERFACE /////////////////////////
-
- $.jStorage = {
- /* Version number */
- version: JSTORAGE_VERSION,
-
- /**
- * Sets a key's value.
- *
- * @param {String} key Key to set. If this value is not set or not
- * a string an exception is raised.
- * @param {Mixed} value Value to set. This can be any value that is JSON
- * compatible (Numbers, Strings, Objects etc.).
- * @param {Object} [options] - possible options to use
- * @param {Number} [options.TTL] - optional TTL value, in milliseconds
- * @return {Mixed} the used value
- */
- set: function(key, value, options) {
- _checkKey(key);
-
- options = options || {};
-
- // undefined values are deleted automatically
- if (typeof value == 'undefined') {
- this.deleteKey(key);
- return value;
- }
-
- if (_XMLService.isXML(value)) {
- value = {
- _is_xml: true,
- xml: _XMLService.encode(value)
- };
- } else if (typeof value == 'function') {
- return undefined; // functions can't be saved!
- } else if (value && typeof value == 'object') {
- // clone the object before saving to _storage tree
- value = JSON.parse(JSON.stringify(value));
- }
-
- _storage[key] = value;
-
- _storage.__jstorage_meta.CRC32[key] = '2.' + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);
-
- this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange
-
- _fireObservers(key, 'updated');
- return value;
- },
-
- /**
- * Looks up a key in cache
- *
- * @param {String} key - Key to look up.
- * @param {mixed} def - Default value to return, if key didn't exist.
- * @return {Mixed} the key value, default value or null
- */
- get: function(key, def) {
- _checkKey(key);
- if (key in _storage) {
- if (_storage[key] && typeof _storage[key] == 'object' && _storage[key]._is_xml) {
- return _XMLService.decode(_storage[key].xml);
- } else {
- return _storage[key];
- }
- }
- return typeof(def) == 'undefined' ? null : def;
- },
-
- /**
- * Deletes a key from cache.
- *
- * @param {String} key - Key to delete.
- * @return {Boolean} true if key existed or false if it didn't
- */
- deleteKey: function(key) {
- _checkKey(key);
- if (key in _storage) {
- delete _storage[key];
- // remove from TTL list
- if (typeof _storage.__jstorage_meta.TTL == 'object' &&
- key in _storage.__jstorage_meta.TTL) {
- delete _storage.__jstorage_meta.TTL[key];
- }
-
- delete _storage.__jstorage_meta.CRC32[key];
-
- _save();
- _publishChange();
- _fireObservers(key, 'deleted');
- return true;
- }
- return false;
- },
-
- /**
- * Sets a TTL for a key, or remove it if ttl value is 0 or below
- *
- * @param {String} key - key to set the TTL for
- * @param {Number} ttl - TTL timeout in milliseconds
- * @return {Boolean} true if key existed or false if it didn't
- */
- setTTL: function(key, ttl) {
- var curtime = +new Date();
- _checkKey(key);
- ttl = Number(ttl) || 0;
- if (key in _storage) {
-
- if (!_storage.__jstorage_meta.TTL) {
- _storage.__jstorage_meta.TTL = {};
- }
-
- // Set TTL value for the key
- if (ttl > 0) {
- _storage.__jstorage_meta.TTL[key] = curtime + ttl;
- } else {
- delete _storage.__jstorage_meta.TTL[key];
- }
-
- _save();
-
- _handleTTL();
-
- _publishChange();
- return true;
- }
- return false;
- },
-
- /**
- * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
- *
- * @param {String} key Key to check
- * @return {Number} Remaining TTL in milliseconds
- */
- getTTL: function(key) {
- var curtime = +new Date(),
- ttl;
- _checkKey(key);
- if (key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]) {
- ttl = _storage.__jstorage_meta.TTL[key] - curtime;
- return ttl || 0;
- }
- return 0;
- },
-
- /**
- * Deletes everything in cache.
- *
- * @return {Boolean} Always true
- */
- flush: function() {
- _storage = {
- __jstorage_meta: {
- CRC32: {}
- }
- };
- _save();
- _publishChange();
- _fireObservers(null, 'flushed');
- return true;
- },
-
- /**
- * Returns a read-only copy of _storage
- *
- * @return {Object} Read-only copy of _storage
- */
- storageObj: function() {
- function F() {}
- F.prototype = _storage;
- return new F();
- },
-
- /**
- * Returns an index of all used keys as an array
- * ['key1', 'key2',..'keyN']
- *
- * @return {Array} Used keys
- */
- index: function() {
- var index = [],
- i;
- for (i in _storage) {
- if (_storage.hasOwnProperty(i) && i != '__jstorage_meta') {
- index.push(i);
- }
- }
- return index;
- },
-
- /**
- * How much space in bytes does the storage take?
- *
- * @return {Number} Storage size in chars (not the same as in bytes,
- * since some chars may take several bytes)
- */
- storageSize: function() {
- return _storage_size;
- },
-
- /**
- * Which backend is currently in use?
- *
- * @return {String} Backend name
- */
- currentBackend: function() {
- return _backend;
- },
-
- /**
- * Test if storage is available
- *
- * @return {Boolean} True if storage can be used
- */
- storageAvailable: function() {
- return !!_backend;
- },
-
- /**
- * Register change listeners
- *
- * @param {String} key Key name
- * @param {Function} callback Function to run when the key changes
- */
- listenKeyChange: function(key, callback) {
- _checkKey(key);
- if (!_observers[key]) {
- _observers[key] = [];
- }
- _observers[key].push(callback);
- },
-
- /**
- * Remove change listeners
- *
- * @param {String} key Key name to unregister listeners against
- * @param {Function} [callback] If set, unregister the callback, if not - unregister all
- */
- stopListening: function(key, callback) {
- _checkKey(key);
-
- if (!_observers[key]) {
- return;
- }
-
- if (!callback) {
- delete _observers[key];
- return;
- }
-
- for (var i = _observers[key].length - 1; i >= 0; i--) {
- if (_observers[key][i] == callback) {
- _observers[key].splice(i, 1);
- }
- }
- },
-
- /**
- * Subscribe to a Publish/Subscribe event stream
- *
- * @param {String} channel Channel name
- * @param {Function} callback Function to run when the something is published to the channel
- */
- subscribe: function(channel, callback) {
- channel = (channel || '').toString();
- if (!channel) {
- throw new TypeError('Channel not defined');
- }
- if (!_pubsub_observers[channel]) {
- _pubsub_observers[channel] = [];
- }
- _pubsub_observers[channel].push(callback);
- },
-
- /**
- * Publish data to an event stream
- *
- * @param {String} channel Channel name
- * @param {Mixed} payload Payload to deliver
- */
- publish: function(channel, payload) {
- channel = (channel || '').toString();
- if (!channel) {
- throw new TypeError('Channel not defined');
- }
-
- _publish(channel, payload);
- },
-
- /**
- * Reloads the data from browser storage
- */
- reInit: function() {
- _reloadData();
- },
-
- /**
- * Removes reference from global objects and saves it as jStorage
- *
- * @param {Boolean} option if needed to save object as simple 'jStorage' in windows context
- */
- noConflict: function(saveInGlobal) {
- delete window.$.jStorage;
-
- if (saveInGlobal) {
- window.jStorage = this;
- }
-
- return this;
- }
- };
-
- // Initialize jStorage
- _init();
-
-})();
+++ /dev/null
-/*!
- * MockJax - jQuery Plugin to Mock Ajax requests
- *
- * Version: 1.4.0
- * Released: 2011-02-04
- * Source: http://github.com/appendto/jquery-mockjax
- * Docs: http://enterprisejquery.com/2010/07/mock-your-ajax-requests-with-mockjax-for-rapid-development
- * Plugin: mockjax
- * Author: Jonathan Sharp (http://jdsharp.com)
- * License: MIT,GPL
- *
- * Copyright (c) 2010 appendTo LLC.
- * Dual licensed under the MIT or GPL licenses.
- * http://appendto.com/open-source-licenses
- */
-(function($) {
- var _ajax = $.ajax,
- mockHandlers = [];
-
- function parseXML(xml) {
- if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
- DOMParser = function() { };
- DOMParser.prototype.parseFromString = function( xmlString ) {
- var doc = new ActiveXObject('Microsoft.XMLDOM');
- doc.async = 'false';
- doc.loadXML( xmlString );
- return doc;
- };
- }
-
- try {
- var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
- if ( $.isXMLDoc( xmlDoc ) ) {
- var err = $('parsererror', xmlDoc);
- if ( err.length == 1 ) {
- throw('Error: ' + $(xmlDoc).text() );
- }
- } else {
- throw('Unable to parse XML');
- }
- } catch( e ) {
- var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
- $(document).trigger('xmlParseError', [ msg ]);
- return undefined;
- }
- return xmlDoc;
- }
-
- $.extend({
- ajax: function(origSettings) {
- var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings),
- mock = false;
- // Iterate over our mock handlers (in registration order) until we find
- // one that is willing to intercept the request
- $.each(mockHandlers, function(k, v) {
- if ( !mockHandlers[k] ) {
- return;
- }
- var m = null;
- // If the mock was registered with a function, let the function decide if we
- // want to mock this request
- if ( $.isFunction(mockHandlers[k]) ) {
- m = mockHandlers[k](s);
- } else {
- m = mockHandlers[k];
- // Inspect the URL of the request and check if the mock handler's url
- // matches the url for this ajax request
- if ( $.isFunction(m.url.test) ) {
- // The user provided a regex for the url, test it
- if ( !m.url.test( s.url ) ) {
- m = null;
- }
- } else {
- // Look for a simple wildcard '*' or a direct URL match
- var star = m.url.indexOf('*');
- if ( ( m.url != '*' && m.url != s.url && star == -1 ) ||
- ( star > -1 && m.url.substr(0, star) != s.url.substr(0, star) ) ) {
- // The url we tested did not match the wildcard *
- m = null;
- }
- }
- if ( m ) {
- // Inspect the data submitted in the request (either POST body or GET query string)
- if ( m.data && s.data ) {
- var identical = false;
- // Deep inspect the identity of the objects
- (function ident(mock, live) {
- // Test for situations where the data is a querystring (not an object)
- if (typeof live === 'string') {
- // Querystring may be a regex
- identical = $.isFunction( mock.test ) ? mock.test(live) : mock == live;
- return identical;
- }
- $.each(mock, function(k, v) {
- if ( live[k] === undefined ) {
- identical = false;
- return false;
- } else {
- identical = true;
- if ( typeof live[k] == 'object' ) {
- return ident(mock[k], live[k]);
- } else {
- if ( $.isFunction( mock[k].test ) ) {
- identical = mock[k].test(live[k]);
- } else {
- identical = ( mock[k] == live[k] );
- }
- return identical;
- }
- }
- });
- })(m.data, s.data);
- // They're not identical, do not mock this request
- if ( identical == false ) {
- m = null;
- }
- }
- // Inspect the request type
- if ( m && m.type && m.type != s.type ) {
- // The request type doesn't match (GET vs. POST)
- m = null;
- }
- }
- }
- if ( m ) {
- mock = true;
-
- // Handle console logging
- var c = $.extend({}, $.mockjaxSettings, m);
- if ( c.log && $.isFunction(c.log) ) {
- c.log('MOCK ' + s.type.toUpperCase() + ': ' + s.url, $.extend({}, s));
- }
-
- var jsre = /=\?(&|$)/, jsc = (new Date()).getTime();
-
- // Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
- // because there isn't an easy hook for the cross domain script tag of jsonp
- if ( s.dataType === "jsonp" ) {
- if ( s.type.toUpperCase() === "GET" ) {
- if ( !jsre.test( s.url ) ) {
- s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?";
- }
- } else if ( !s.data || !jsre.test(s.data) ) {
- s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
- }
- s.dataType = "json";
- }
-
- // Build temporary JSONP function
- if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
- jsonp = s.jsonpCallback || ("jsonp" + jsc++);
-
- // Replace the =? sequence both in the query string and the data
- if ( s.data ) {
- s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
- }
-
- s.url = s.url.replace(jsre, "=" + jsonp + "$1");
-
- // We need to make sure
- // that a JSONP style response is executed properly
- s.dataType = "script";
-
- // Handle JSONP-style loading
- window[ jsonp ] = window[ jsonp ] || function( tmp ) {
- data = tmp;
- success();
- complete();
- // Garbage collect
- window[ jsonp ] = undefined;
-
- try {
- delete window[ jsonp ];
- } catch(e) {}
-
- if ( head ) {
- head.removeChild( script );
- }
- };
- }
-
- var rurl = /^(\w+:)?\/\/([^\/?#]+)/,
- parts = rurl.exec( s.url ),
- remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
-
- // Test if we are going to create a script tag (if so, intercept & mock)
- if ( s.dataType === "script" && s.type.toUpperCase() === "GET" && remote ) {
- // Synthesize the mock request for adding a script tag
- var callbackContext = origSettings && origSettings.context || s;
-
- function success() {
- // If a local callback was specified, fire it and pass it the data
- if ( s.success ) {
- s.success.call( callbackContext, ( m.response ? m.response.toString() : m.responseText || ''), status, {} );
- }
-
- // Fire the global callback
- if ( s.global ) {
- trigger( "ajaxSuccess", [{}, s] );
- }
- }
-
- function complete() {
- // Process result
- if ( s.complete ) {
- s.complete.call( callbackContext, {} , status );
- }
-
- // The request was completed
- if ( s.global ) {
- trigger( "ajaxComplete", [{}, s] );
- }
-
- // Handle the global AJAX counter
- if ( s.global && ! --jQuery.active ) {
- jQuery.event.trigger( "ajaxStop" );
- }
- }
-
- function trigger(type, args) {
- (s.context ? jQuery(s.context) : jQuery.event).trigger(type, args);
- }
-
- if ( m.response && $.isFunction(m.response) ) {
- m.response(origSettings);
- } else {
- $.globalEval(m.responseText);
- }
- success();
- complete();
- return false;
- }
- mock = _ajax.call($, $.extend(true, {}, origSettings, {
- // Mock the XHR object
- xhr: function() {
- // Extend with our default mockjax settings
- m = $.extend({}, $.mockjaxSettings, m);
-
- if ( m.contentType ) {
- m.headers['content-type'] = m.contentType;
- }
-
- // Return our mock xhr object
- return {
- status: m.status,
- readyState: 1,
- open: function() { },
- send: function() {
- // This is a substitute for < 1.4 which lacks $.proxy
- var process = (function(that) {
- return function() {
- return (function() {
- // The request has returned
- this.status = m.status;
- this.readyState = 4;
-
- // We have an executable function, call it to give
- // the mock handler a chance to update it's data
- if ( $.isFunction(m.response) ) {
- m.response(origSettings);
- }
- // Copy over our mock to our xhr object before passing control back to
- // jQuery's onreadystatechange callback
- if ( s.dataType == 'json' && ( typeof m.responseText == 'object' ) ) {
- this.responseText = JSON.stringify(m.responseText);
- } else if ( s.dataType == 'xml' ) {
- if ( typeof m.responseXML == 'string' ) {
- this.responseXML = parseXML(m.responseXML);
- } else {
- this.responseXML = m.responseXML;
- }
- } else {
- this.responseText = m.responseText;
- }
- // jQuery < 1.4 doesn't have onreadystate change for xhr
- if ( $.isFunction(this.onreadystatechange) ) {
- this.onreadystatechange( m.isTimeout ? 'timeout' : undefined );
- }
- }).apply(that);
- };
- })(this);
-
- if ( m.proxy ) {
- // We're proxying this request and loading in an external file instead
- _ajax({
- global: false,
- url: m.proxy,
- type: m.proxyType,
- data: m.data,
- dataType: s.dataType,
- complete: function(xhr, txt) {
- m.responseXML = xhr.responseXML;
- m.responseText = xhr.responseText;
- this.responseTimer = setTimeout(process, m.responseTime || 0);
- }
- });
- } else {
- // type == 'POST' || 'GET' || 'DELETE'
- if ( s.async === false ) {
- // TODO: Blocking delay
- process();
- } else {
- this.responseTimer = setTimeout(process, m.responseTime || 50);
- }
- }
- },
- abort: function() {
- clearTimeout(this.responseTimer);
- },
- setRequestHeader: function() { },
- getResponseHeader: function(header) {
- // 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
- if ( m.headers && m.headers[header] ) {
- // Return arbitrary headers
- return m.headers[header];
- } else if ( header.toLowerCase() == 'last-modified' ) {
- return m.lastModified || (new Date()).toString();
- } else if ( header.toLowerCase() == 'etag' ) {
- return m.etag || '';
- } else if ( header.toLowerCase() == 'content-type' ) {
- return m.contentType || 'text/plain';
- }
- },
- getAllResponseHeaders: function() {
- var headers = '';
- $.each(m.headers, function(k, v) {
- headers += k + ': ' + v + "\n";
- });
- return headers;
- }
- };
- }
- }));
- return false;
- }
- });
- // We don't have a mock request, trigger a normal request
- if ( !mock ) {
- return _ajax.apply($, arguments);
- } else {
- return mock;
- }
- }
- });
-
- $.mockjaxSettings = {
- //url: null,
- //type: 'GET',
- log: function(msg) {
- window['console'] && window.console.log && window.console.log(msg);
- },
- status: 200,
- responseTime: 500,
- isTimeout: false,
- contentType: 'text/plain',
- response: '',
- responseText: '',
- responseXML: '',
- proxy: '',
- proxyType: 'GET',
-
- lastModified: null,
- etag: '',
- headers: {
- etag: 'IJF@H#@923uf8023hFO@I#H#',
- 'content-type' : 'text/plain'
- }
- };
-
- $.mockjax = function(settings) {
- var i = mockHandlers.length;
- mockHandlers[i] = settings;
- return i;
- };
- $.mockjaxClear = function(i) {
- if ( arguments.length == 1 ) {
- mockHandlers[i] = null;
- } else {
- mockHandlers = [];
- }
- };
-})(jQuery);
+++ /dev/null
-/*!
- * jQuery xmlDOM Plugin v1.0
- * http://outwestmedia.com/jquery-plugins/xmldom/
- *
- * Released: 2009-04-06
- * Version: 1.0
- *
- * Copyright (c) 2009 Jonathan Sharp, Out West Media LLC.
- * Dual licensed under the MIT and GPL licenses.
- * http://docs.jquery.com/License
- */
-(function($) {
- // IE DOMParser wrapper
- if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
- DOMParser = function() { };
- DOMParser.prototype.parseFromString = function( xmlString ) {
- var doc = new ActiveXObject('Microsoft.XMLDOM');
- doc.async = 'false';
- doc.loadXML( xmlString );
- return doc;
- };
- }
-
- $.xmlDOM = function(xml, onErrorFn) {
- try {
- var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
- if ( $.isXMLDoc( xmlDoc ) ) {
- var err = $('parsererror', xmlDoc);
- if ( err.length == 1 ) {
- throw('Error: ' + $(xmlDoc).text() );
- }
- } else {
- throw('Unable to parse XML');
- }
- } catch( e ) {
- var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
- if ( $.isFunction( onErrorFn ) ) {
- onErrorFn( msg );
- } else {
- $(document).trigger('xmlParseError', [ msg ]);
- }
- return $([]);
- }
- return $( xmlDoc );
- };
-})(jQuery);
\ No newline at end of file
* @param {HTMLElement} titleElement Element with the title to update (may be the same as `element`)
*/
function updateTooltipOnElement( element, titleElement ) {
- var oldTitle, parts, regexp, newTitle, accessKeyLabel;
+ var oldTitle, parts, regexp, newTitle, accessKeyLabel,
+ separatorMsg = mw.message( 'word-separator' ).plain();
oldTitle = titleElement.title;
if ( !oldTitle ) {
return;
}
- parts = ( mw.msg( 'word-separator' ) + mw.msg( 'brackets' ) ).split( '$1' );
+ parts = ( separatorMsg + mw.message( 'brackets' ).plain() ).split( '$1' );
regexp = new RegExp( parts.map( mw.RegExp.escape ).join( '.*?' ) + '$' );
newTitle = oldTitle.replace( regexp, '' );
accessKeyLabel = getAccessKeyLabel( element );
if ( accessKeyLabel ) {
// Should be build the same as in Linker::titleAttrib
- newTitle += mw.msg( 'word-separator' ) + mw.msg( 'brackets', accessKeyLabel );
+ newTitle += separatorMsg + mw.message( 'brackets', accessKeyLabel ).plain();
}
if ( oldTitle !== newTitle ) {
titleElement.title = newTitle;
}
}
-/* Align the toggle based on the direction of the content language */
+/* Collapsible elements in the UI (outside of the content area) are not in either .mw-content-ltr or
+ * .mw-content-rtl. Align them based on the user language. */
+.mw-collapsible:not( @{exclude} ) th:before,
+.mw-collapsible:not( @{exclude} ):before,
+.mw-collapsible-toggle {
+ float: right;
+}
+
+/* For collapsible elements in the content area, override the alginment based on the content language. */
/* @noflip */
.mw-content-ltr,
.mw-content-rtl .mw-content-ltr {
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 {
/**
unicode-bidi: embed;
}
+.mw-diff-slot-header {
+ text-align: center;
+}
+
/*!
* Wikidiff2 rendering for moved paragraphs
*/
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;
} );
}
*/
inspect.dumpTable = function ( data ) {
try {
- // Bartosz made me put this here.
- if ( window.opera ) { throw window.opera; }
// Use Function.prototype#call to force an exception on Firefox,
// which doesn't define console#table but doesn't complain if you
// try to invoke it.
} catch ( e ) {}
try {
console.log( JSON.stringify( data, null, 2 ) );
- return;
} catch ( e ) {}
- mw.log( data );
};
/**
*
* 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( {
} else if ( summaryByteLimit ) {
mw.widgets.visibleByteLimit( wpReason, summaryByteLimit );
}
- // Infuse for nicer "help" popup
- if ( $( '#wpMovetalk-field' ).length ) {
- OO.ui.infuse( $( '#wpMovetalk-field' ) );
- }
} );
}( mediaWiki, jQuery ) );
* @class mw.user
* @singleton
*/
-/* global Uint32Array */
+/* global Uint16Array */
( function ( mw, $ ) {
- var userInfoPromise, stickyRandomSessionId;
+ var userInfoPromise, pageviewRandomId;
/**
* Get the current user's groups or rights
* 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 );
},
/**
* A sticky generateRandomSessionId for the current JS execution context,
- * cached within this class.
+ * cached within this class (also known as a page view token).
*
+ * @since 1.32
* @return {string} 64 bit integer in hex format, padded
*/
- stickyRandomId: function () {
- if ( !stickyRandomSessionId ) {
- stickyRandomSessionId = mw.user.generateRandomSessionId();
+ getPageviewToken: function () {
+ if ( !pageviewRandomId ) {
+ pageviewRandomId = mw.user.generateRandomSessionId();
}
- return stickyRandomSessionId;
+ return pageviewRandomId;
},
/**
}
} );
+ /**
+ * @method stickyRandomId
+ * @deprecated since 1.32 use getPageviewToken instead
+ */
+ mw.log.deprecate( mw.user, 'stickyRandomId', mw.user.getPageviewToken, 'Please use getPageviewToken instead' );
+
}( mediaWiki, jQuery ) );
--- /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();
}() );
'TestDeprecatedSubclass' => "$testDir/phpunit/includes/debug/TestDeprecatedSubclass.php",
# tests/phpunit/includes/diff
+ 'CustomDifferenceEngine' => "$testDir/phpunit/includes/diff/CustomDifferenceEngine.php",
'FakeDiffOp' => "$testDir/phpunit/includes/diff/FakeDiffOp.php",
# tests/phpunit/includes/externalstore
) {
return new ParserOutput;
}
+
+ public function getOutput() {
+ return new ParserOutput;
+ }
}
class_exists( PHPUnit_TextUI_Command::class ) ? [] : [ 'tests/phan/stubs/phpunit4.php' ],
[
'maintenance/7zip.inc',
- 'maintenance/backup.inc',
'maintenance/cleanupTable.inc',
'maintenance/CodeCleanerGlobalsPass.inc',
'maintenance/commandLine.inc',
"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 ) );
}
+++ /dev/null
-# Changes
-DELETE {
-?category ?x ?y
-} WHERE {
- VALUES ?category {
- <http://acme.test/wiki/Category:Changed_category>
- }
-};
-INSERT DATA {
-
-<http://acme.test/wiki/Category:Changed_category> a mediawiki:Category ;
- rdfs:label "Changed category" ;
- mediawiki:pages "7"^^xsd:integer ;
- mediawiki:subcategories "2"^^xsd:integer ;
- mediawiki:isInCategory <http://acme.test/wiki/Category:Parent_of_30> .
-
-};
--- /dev/null
+# Edits
+DELETE {
+?category ?x ?y
+} WHERE {
+ VALUES ?category {
+ <http://acme.test/wiki/Category:Changed_category>
+ }
+};
+INSERT DATA {
+
+<http://acme.test/wiki/Category:Changed_category> a mediawiki:Category ;
+ rdfs:label "Changed category" ;
+ mediawiki:pages "7"^^xsd:integer ;
+ mediawiki:subcategories "2"^^xsd:integer ;
+ mediawiki:isInCategory <http://acme.test/wiki/Category:Parent_of_30> .
+
+};
* per T28425
* @covers Block::__construct
*/
- public function testBug26425BlockTimestampDefaultsToTime() {
+ public function testT28425BlockTimestampDefaultsToTime() {
$user = $this->getUserForBlocking();
$block = $this->addBlockForUser( $user );
$madeAt = wfTimestamp( TS_MW );
* because the new function didn't accept empty strings like Block::load()
* had. Regression T31116.
*
- * @dataProvider provideBug29116Data
+ * @dataProvider provideT31116Data
* @covers Block::newFromTarget
*/
- public function testBug29116NewFromTargetWithEmptyIp( $vagueTarget ) {
+ public function testT31116NewFromTargetWithEmptyIp( $vagueTarget ) {
$user = $this->getUserForBlocking();
$initialBlock = $this->addBlockForUser( $user );
$block = Block::newFromTarget( $user->getName(), $vagueTarget );
);
}
- public static function provideBug29116Data() {
+ public static function provideT31116Data() {
return [
[ null ],
[ '' ],
* @covers ::wfShellExec
*/
class WfShellExecTest extends MediaWikiTestCase {
- public function testBug67870() {
+ public function testT69870() {
$command = wfIsWindows()
// 333 = 331 + CRLF
? ( 'for /l %i in (1, 1, 1001) do @echo ' . str_repeat( '*', 331 ) )
<?php
class HtmlTest extends MediaWikiTestCase {
+ private $restoreWarnings;
protected function setUp() {
parent::setUp();
] );
$this->setUserLang( $langObj );
$this->setContentLang( $langObj );
+ $this->restoreWarnings = false;
+ }
+
+ protected function tearDown() {
+ if ( $this->restoreWarnings ) {
+ $this->restoreWarnings = false;
+ Wikimedia\restoreWarnings();
+ }
+ parent::tearDown();
}
/**
],
'Ampersand' => [
'EXAMPLE.is(a && b);',
- '<script>/*<![CDATA[*/EXAMPLE.is(a && b);/*]]>*/</script>'
+ '<script>EXAMPLE.is(a && b);</script>'
],
'HTML' => [
'EXAMPLE.label("<a>");',
- '<script>/*<![CDATA[*/EXAMPLE.label("<a>");/*]]>*/</script>'
+ '<script>EXAMPLE.label("<a>");</script>'
],
- 'Script closing string' => [
+ 'Script closing string (lower)' => [
'EXAMPLE.label("</script>");',
- // Broken: First </script> ends the script in HTML
- '<script>/*<![CDATA[*/EXAMPLE.label("</script>");/*]]>*/</script>'
+ '<script>/* ERROR: Invalid script */</script>',
+ true,
],
- 'CDATA string' => [
- 'EXAMPLE.label("&> CDATA ]]>");',
- // Broken: Works in HTML, but is invalid XML.
- '<script>/*<![CDATA[*/EXAMPLE.label("&> CDATA ]]>");/*]]>*/</script>'
+ 'Script closing with non-standard attributes (mixed)' => [
+ 'EXAMPLE.label("</SCriPT and STyLE>");',
+ '<script>/* ERROR: Invalid script */</script>',
+ true,
+ ],
+ 'HTML-comment-open and script-open' => [
+ // In HTML, <script> contents aren't just plain CDATA until </script>,
+ // there are levels of escaping modes, and the below sequence puts an
+ // HTML parser in a state where </script> would *not* close the script.
+ // https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escape-end-state
+ 'var a = "<!--<script>";',
+ '<script>/* ERROR: Invalid script */</script>',
+ true,
],
];
}
* @dataProvider provideInlineScript
* @covers Html::inlineScript
*/
- public function testInlineScript( $code, $expected ) {
+ public function testInlineScript( $code, $expected, $error = false ) {
+ if ( $error ) {
+ Wikimedia\suppressWarnings();
+ $this->restoreWarnings = true;
+ }
$this->assertSame( Html::inlineScript( $code ), $expected );
}
}
] );
}
- /**
- * @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
}
"no edit conflict expected here" );
}
- public function testEditConflict_bug41990() {
+ public function testEditConflict_T43990() {
static $count = 0;
$count++;
*/
// assume NS_HELP defaults to wikitext
- $name = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_$count";
+ $name = "Help:ApiEditPageTest_testEditConflict_redirect_T43990_$count";
$title = Title::newFromText( $name );
$page = WikiPage::factory( $title );
- $rname = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_r$count";
+ $rname = "Help:ApiEditPageTest_testEditConflict_redirect_T43990_r$count";
$rtitle = Title::newFromText( $rname );
$rpage = WikiPage::factory( $rtitle );
$expectedEnd = "</div>";
$this->assertSame( $expectedEnd, substr( $html, -strlen( $expectedEnd ) ) );
+ $unexpectedEnd = '#<!-- \nNewPP limit report|' .
+ '<!--\nTransclusion expansion time report#s';
+ $this->assertNotRegExp( $unexpectedEnd, $html );
+
$html = substr( $html, 0, strlen( $html ) - strlen( $expectedEnd ) );
} else {
$expectedEnd = '#\n<!-- \nNewPP limit report\n(?>.+?\n-->)\n' .
'<!--\nTransclusion expansion time report \(%,ms,calls,template\)\n(?>.*?\n-->)\n' .
- '</div>(\n<!-- Saved in parser cache (?>.*?\n -->)\n)?$#s';
+ '(\n<!-- Saved in parser cache (?>.*?\n -->)\n)?</div>$#s';
$this->assertRegExp( $expectedEnd, $html );
$html = preg_replace( $expectedEnd, '', $html );
<?php
+use Wikimedia\TestingAccessWrapper;
+
/**
* @covers ApiStashEdit
* @group API
* @group Database
*/
class ApiStashEditTest extends ApiTestCase {
+ public function setUp() {
+ parent::setUp();
+
+ // We need caching here, but note that the cache gets cleared in between tests, so it
+ // doesn't work with @depends
+ $this->setMwGlobals( 'wgMainCacheType', 'hash' );
+ }
+
+ /**
+ * Make a stashedit API call with suitable default parameters
+ *
+ * @param array $params Query parameters for API request. All are optional and will have
+ * sensible defaults filled in. To make a parameter actually not passed, set to null.
+ * @param User $user User to do the request
+ * @param string $expectedResult 'stashed', 'editconflict'
+ */
+ protected function doStash(
+ array $params = [], User $user = null, $expectedResult = 'stashed'
+ ) {
+ $params = array_merge( [
+ 'action' => 'stashedit',
+ 'title' => __CLASS__,
+ 'contentmodel' => 'wikitext',
+ 'contentformat' => 'text/x-wiki',
+ 'baserevid' => 0,
+ ], $params );
+ if ( !array_key_exists( 'text', $params ) &&
+ !array_key_exists( 'stashedtexthash', $params )
+ ) {
+ $params['text'] = 'Content';
+ }
+ foreach ( $params as $key => $val ) {
+ if ( $val === null ) {
+ unset( $params[$key] );
+ }
+ }
+
+ if ( isset( $params['text'] ) ) {
+ $expectedText = $params['text'];
+ } elseif ( isset( $params['stashedtexthash'] ) ) {
+ $expectedText = $this->getStashedText( $params['stashedtexthash'] );
+ }
+ if ( isset( $expectedText ) ) {
+ $expectedText = rtrim( str_replace( "\r\n", "\n", $expectedText ) );
+ $expectedHash = sha1( $expectedText );
+ $origText = $this->getStashedText( $expectedHash );
+ }
+
+ $res = $this->doApiRequestWithToken( $params, null, $user );
+
+ $this->assertSame( $expectedResult, $res[0]['stashedit']['status'] );
+ $this->assertCount( $expectedResult === 'stashed' ? 2 : 1, $res[0]['stashedit'] );
+
+ if ( $expectedResult === 'stashed' ) {
+ $hash = $res[0]['stashedit']['texthash'];
+
+ $this->assertSame( $expectedText, $this->getStashedText( $hash ) );
+
+ $this->assertSame( $expectedHash, $hash );
+
+ if ( isset( $params['stashedtexthash'] ) ) {
+ $this->assertSame( $params['stashedtexthash'], $expectedHash, 'Sanity' );
+ }
+ } else {
+ $this->assertSame( $origText, $this->getStashedText( $expectedHash ) );
+ }
+
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+
+ return $res;
+ }
+
+ /**
+ * Return the text stashed for $hash.
+ *
+ * @param string $hash
+ * @return string
+ */
+ protected function getStashedText( $hash ) {
+ $cache = ObjectCache::getLocalClusterInstance();
+ $key = $cache->makeKey( 'stashedit', 'text', $hash );
+ return $cache->get( $key );
+ }
+
+ /**
+ * Return a key that can be passed to the cache to obtain a PreparedEdit object.
+ *
+ * @param string $title Title of page
+ * @param string Content $text Content of edit
+ * @param User $user User who made edit
+ * @return string
+ */
+ protected function getStashKey( $title = __CLASS__, $text = 'Content', User $user = null ) {
+ $titleObj = Title::newFromText( $title );
+ $content = new WikitextContent( $text );
+ if ( !$user ) {
+ $user = $this->getTestSysop()->getUser();
+ }
+ $wrapper = TestingAccessWrapper::newFromClass( ApiStashEdit::class );
+ return $wrapper->getStashKey( $titleObj, $wrapper->getContentHash( $content ), $user );
+ }
public function testBasicEdit() {
- $apiResult = $this->doApiRequestWithToken(
- [
- 'action' => 'stashedit',
- 'title' => 'ApistashEdit_Page',
- 'contentmodel' => 'wikitext',
- 'contentformat' => 'text/x-wiki',
- 'text' => 'Text for ' . __METHOD__ . ' page',
- 'baserevid' => 0,
- ]
+ $this->doStash();
+ }
+
+ public function testBot() {
+ // @todo This restriction seems arbitrary, is there any good reason to keep it?
+ $this->setExpectedApiException( 'apierror-botsnotsupported' );
+
+ $this->doStash( [], $this->getTestUser( [ 'bot' ] )->getUser() );
+ }
+
+ public function testUnrecognizedFormat() {
+ $this->setExpectedApiException(
+ [ 'apierror-badformat-generic', 'application/json', 'wikitext' ] );
+
+ $this->doStash( [ 'contentformat' => 'application/json' ] );
+ }
+
+ public function testMissingTextAndStashedTextHash() {
+ $this->setExpectedApiException( [
+ 'apierror-missingparam-one-of',
+ Message::listParam( [ '<var>stashedtexthash</var>', '<var>text</var>' ] ),
+ 2
+ ] );
+ $this->doStash( [ 'text' => null ] );
+ }
+
+ public function testStashedTextHash() {
+ $res = $this->doStash();
+
+ $this->doStash( [ 'stashedtexthash' => $res[0]['stashedit']['texthash'] ] );
+ }
+
+ public function testMalformedStashedTextHash() {
+ $this->setExpectedApiException( 'apierror-stashedit-missingtext' );
+ $this->doStash( [ 'stashedtexthash' => 'abc' ] );
+ }
+
+ public function testMissingStashedTextHash() {
+ $this->setExpectedApiException( 'apierror-stashedit-missingtext' );
+ $this->doStash( [ 'stashedtexthash' => str_repeat( '0', 40 ) ] );
+ }
+
+ public function testHashNormalization() {
+ $res1 = $this->doStash( [ 'text' => "a\r\nb\rc\nd \t\n\r" ] );
+ $res2 = $this->doStash( [ 'text' => "a\nb\rc\nd" ] );
+
+ $this->assertSame( $res1[0]['stashedit']['texthash'], $res2[0]['stashedit']['texthash'] );
+ $this->assertSame( "a\nb\rc\nd",
+ $this->getStashedText( $res1[0]['stashedit']['texthash'] ) );
+ }
+
+ public function testNonexistentBaseRevId() {
+ $this->setExpectedApiException( [ 'apierror-nosuchrevid', pow( 2, 31 ) - 1 ] );
+
+ $name = ucfirst( __FUNCTION__ );
+ $this->editPage( $name, '' );
+ $this->doStash( [ 'title' => $name, 'baserevid' => pow( 2, 31 ) - 1 ] );
+ }
+
+ public function testPageWithNoRevisions() {
+ $name = ucfirst( __FUNCTION__ );
+ $rev = $this->editPage( $name, '' )->value['revision'];
+
+ $this->setExpectedApiException( [ 'apierror-missingrev-pageid', $rev->getPage() ] );
+
+ // Corrupt the database. @todo Does the API really need to fail gracefully for this case?
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update(
+ 'page',
+ [ 'page_latest' => 0 ],
+ [ 'page_id' => $rev->getPage() ],
+ __METHOD__
);
- $apiResult = $apiResult[0];
- $this->assertArrayHasKey( 'stashedit', $apiResult );
- $this->assertEquals( 'stashed', $apiResult['stashedit']['status'] );
+
+ $this->doStash( [ 'title' => $name, 'baserevid' => $rev->getId() ] );
+ }
+
+ public function testExistingPage() {
+ $name = ucfirst( __FUNCTION__ );
+ $rev = $this->editPage( $name, '' )->value['revision'];
+
+ $this->doStash( [ 'title' => $name, 'baserevid' => $rev->getId() ] );
+ }
+
+ public function testInterveningEdit() {
+ $name = ucfirst( __FUNCTION__ );
+ $oldRev = $this->editPage( $name, "A\n\nB" )->value['revision'];
+ $this->editPage( $name, "A\n\nC" );
+
+ $this->doStash( [
+ 'title' => $name,
+ 'baserevid' => $oldRev->getId(),
+ 'text' => "D\n\nB",
+ ] );
+ }
+
+ public function testEditConflict() {
+ $name = ucfirst( __FUNCTION__ );
+ $oldRev = $this->editPage( $name, 'A' )->value['revision'];
+ $this->editPage( $name, 'B' );
+
+ $this->doStash( [
+ 'title' => $name,
+ 'baserevid' => $oldRev->getId(),
+ 'text' => 'C',
+ ], null, 'editconflict' );
+ }
+
+ public function testDeletedRevision() {
+ $name = ucfirst( __FUNCTION__ );
+ $oldRev = $this->editPage( $name, 'A' )->value['revision'];
+ $this->editPage( $name, 'B' );
+
+ $this->setExpectedApiException( [ 'apierror-missingcontent-pageid', $oldRev->getPage() ] );
+
+ $this->revisionDelete( $oldRev );
+
+ $this->doStash( [
+ 'title' => $name,
+ 'baserevid' => $oldRev->getId(),
+ 'text' => 'C',
+ ] );
}
+ public function testDeletedRevisionSection() {
+ $name = ucfirst( __FUNCTION__ );
+ $oldRev = $this->editPage( $name, 'A' )->value['revision'];
+ $this->editPage( $name, 'B' );
+
+ $this->setExpectedApiException( 'apierror-sectionreplacefailed' );
+
+ $this->revisionDelete( $oldRev );
+
+ $this->doStash( [
+ 'title' => $name,
+ 'baserevid' => $oldRev->getId(),
+ 'text' => 'C',
+ 'section' => '1',
+ ] );
+ }
+
+ public function testPingLimiter() {
+ global $wgRateLimits;
+
+ $this->stashMwGlobals( 'wgRateLimits' );
+ $wgRateLimits['stashedit'] = [ '&can-bypass' => false, 'user' => [ 1, 60 ] ];
+
+ $this->doStash( [ 'text' => 'A' ] );
+
+ $this->doStash( [ 'text' => 'B' ], null, 'ratelimited' );
+ }
+
+ /**
+ * Shortcut for calling ApiStashEdit::checkCache() without having to create Titles and Contents
+ * in every test.
+ *
+ * @param User $user
+ * @param string $text The text of the article
+ * @return stdClass|bool Return value of ApiStashEdit::checkCache(), false if not in cache
+ */
+ protected function doCheckCache( User $user, $text = 'Content' ) {
+ return ApiStashEdit::checkCache(
+ Title::newFromText( __CLASS__ ),
+ new WikitextContent( $text ),
+ $user
+ );
+ }
+
+ public function testCheckCache() {
+ $user = $this->getMutableTestUser()->getUser();
+
+ $this->doStash( [], $user );
+
+ $this->assertInstanceOf( stdClass::class, $this->doCheckCache( $user ) );
+
+ // Another user doesn't see the cache
+ $this->assertFalse(
+ $this->doCheckCache( $this->getTestUser()->getUser() ),
+ 'Cache is user-specific'
+ );
+
+ // Nor does the original one if they become a bot
+ $user->addGroup( 'bot' );
+ $this->assertFalse(
+ $this->doCheckCache( $user ),
+ "We assume bots don't have cache entries"
+ );
+
+ // But other groups are okay
+ $user->removeGroup( 'bot' );
+ $user->addGroup( 'sysop' );
+ $this->assertInstanceOf( stdClass::class, $this->doCheckCache( $user ) );
+ }
+
+ public function testCheckCacheAnon() {
+ $user = new User();
+
+ $this->doStash( [], $user );
+
+ $this->assertInstanceOf( stdClass::class, $this->docheckCache( $user ) );
+ }
+
+ /**
+ * Stash an edit some time in the past, for testing expiry and freshness logic.
+ *
+ * @param User $user Who's doing the editing
+ * @param string $text What text should be cached
+ * @param int $howOld How many seconds is "old" (we actually set it one second before this)
+ */
+ protected function doStashOld(
+ User $user, $text = 'Content', $howOld = ApiStashEdit::PRESUME_FRESH_TTL_SEC
+ ) {
+ $this->doStash( [ 'text' => $text ], $user );
+
+ // Monkey with the cache to make the edit look old. @todo Is there a less fragile way to
+ // fake the time?
+ $key = $this->getStashKey( __CLASS__, $text, $user );
+
+ $cache = ObjectCache::getLocalClusterInstance();
+
+ $editInfo = $cache->get( $key );
+ $editInfo->output->setCacheTime( wfTimestamp( TS_MW,
+ wfTimestamp( TS_UNIX, $editInfo->output->getCacheTime() ) - $howOld - 1 ) );
+
+ $cache->set( $key, $editInfo );
+ }
+
+ public function testCheckCacheOldNoEdits() {
+ $user = $this->getTestSysop()->getUser();
+
+ $this->doStashOld( $user );
+
+ // Should still be good, because no intervening edits
+ $this->assertInstanceOf( stdClass::class, $this->doCheckCache( $user ) );
+ }
+
+ public function testCheckCacheOldNoEditsAnon() {
+ // Specify a made-up IP address to make sure no edits are lying around
+ $user = User::newFromName( '192.0.2.77', false );
+
+ $this->doStashOld( $user );
+
+ // Should still be good, because no intervening edits
+ $this->assertInstanceOf( stdClass::class, $this->doCheckCache( $user ) );
+ }
+
+ public function testCheckCacheInterveningEdits() {
+ $user = $this->getTestSysop()->getUser();
+
+ $this->doStashOld( $user );
+
+ // Now let's also increment our editcount
+ $this->editPage( ucfirst( __FUNCTION__ ), '' );
+
+ $this->assertFalse( $this->doCheckCache( $user ),
+ "Cache should be invalidated when it's old and the user has an intervening edit" );
+ }
+
+ /**
+ * @dataProvider signatureProvider
+ * @param string $text Which signature to test (~~~, ~~~~, or ~~~~~)
+ * @param int $ttl Expected TTL in seconds
+ */
+ public function testSignatureTtl( $text, $ttl ) {
+ $this->doStash( [ 'text' => $text ] );
+
+ $cache = ObjectCache::getLocalClusterInstance();
+ $key = $this->getStashKey( __CLASS__, $text );
+
+ $wrapper = TestingAccessWrapper::newFromObject( $cache );
+
+ $this->assertEquals( $ttl, $wrapper->bag[$key][HashBagOStuff::KEY_EXP] - time(), '', 1 );
+ }
+
+ public function signatureProvider() {
+ return [
+ '~~~' => [ '~~~', ApiStashEdit::MAX_SIGNATURE_TTL ],
+ '~~~~' => [ '~~~~', ApiStashEdit::MAX_SIGNATURE_TTL ],
+ '~~~~~' => [ '~~~~~', ApiStashEdit::MAX_SIGNATURE_TTL ],
+ ];
+ }
+
+ public function testIsInternal() {
+ $res = $this->doApiRequest( [
+ 'action' => 'paraminfo',
+ 'modules' => 'stashedit',
+ ] );
+
+ $this->assertCount( 1, $res[0]['paraminfo']['modules'] );
+ $this->assertSame( true, $res[0]['paraminfo']['modules'][0]['internal'] );
+ }
+
+ public function testBusy() {
+ // @todo This doesn't work because both lock acquisitions are in the same MySQL session, so
+ // they don't conflict. How do I open a different session?
+ $this->markTestSkipped();
+
+ $key = $this->getStashKey();
+ $this->db->lock( $key, __METHOD__, 0 );
+ try {
+ $this->doStash( [], null, 'busy' );
+ } finally {
+ $this->db->unlock( $key, __METHOD__ );
+ }
+ }
}
<?php
use MediaWiki\MediaWikiServices;
+use Wikimedia\TestingAccessWrapper;
/**
* @group ContentHandler
} );
$this->assertContains( 'Ferrari', ContentHandler::getContentModels() );
}
+
+ /**
+ * @covers ContentHandler::getSlotDiffRenderer
+ */
+ public function testGetSlotDiffRenderer_default() {
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'GetSlotDiffRenderer' => [],
+ ] );
+
+ // test default renderer
+ $contentHandler = new WikitextContentHandler( CONTENT_MODEL_WIKITEXT );
+ $slotDiffRenderer = $contentHandler->getSlotDiffRenderer( RequestContext::getMain() );
+ $this->assertInstanceOf( TextSlotDiffRenderer::class, $slotDiffRenderer );
+ }
+
+ /**
+ * @covers ContentHandler::getSlotDiffRenderer
+ */
+ public function testGetSlotDiffRenderer_bc() {
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'GetSlotDiffRenderer' => [],
+ ] );
+
+ // test B/C renderer
+ $customDifferenceEngine = $this->getMockBuilder( DifferenceEngine::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ // hack to track object identity across cloning
+ $customDifferenceEngine->objectId = 12345;
+ $customContentHandler = $this->getMockBuilder( ContentHandler::class )
+ ->setConstructorArgs( [ 'foo', [] ] )
+ ->setMethods( [ 'createDifferenceEngine' ] )
+ ->getMockForAbstractClass();
+ $customContentHandler->expects( $this->any() )
+ ->method( 'createDifferenceEngine' )
+ ->willReturn( $customDifferenceEngine );
+ /** @var $customContentHandler ContentHandler */
+ $slotDiffRenderer = $customContentHandler->getSlotDiffRenderer( RequestContext::getMain() );
+ $this->assertInstanceOf( DifferenceEngineSlotDiffRenderer::class, $slotDiffRenderer );
+ $this->assertSame(
+ $customDifferenceEngine->objectId,
+ TestingAccessWrapper::newFromObject( $slotDiffRenderer )->differenceEngine->objectId
+ );
+ }
+
+ /**
+ * @covers ContentHandler::getSlotDiffRenderer
+ */
+ public function testGetSlotDiffRenderer_nobc() {
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'GetSlotDiffRenderer' => [],
+ ] );
+
+ // test that B/C renderer does not get used when getSlotDiffRendererInternal is overridden
+ $customDifferenceEngine = $this->getMockBuilder( DifferenceEngine::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $customSlotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $customContentHandler2 = $this->getMockBuilder( ContentHandler::class )
+ ->setConstructorArgs( [ 'bar', [] ] )
+ ->setMethods( [ 'createDifferenceEngine', 'getSlotDiffRendererInternal' ] )
+ ->getMockForAbstractClass();
+ $customContentHandler2->expects( $this->any() )
+ ->method( 'createDifferenceEngine' )
+ ->willReturn( $customDifferenceEngine );
+ $customContentHandler2->expects( $this->any() )
+ ->method( 'getSlotDiffRendererInternal' )
+ ->willReturn( $customSlotDiffRenderer );
+ /** @var $customContentHandler2 ContentHandler */
+ $slotDiffRenderer = $customContentHandler2->getSlotDiffRenderer( RequestContext::getMain() );
+ $this->assertSame( $customSlotDiffRenderer, $slotDiffRenderer );
+ }
+
+ /**
+ * @covers ContentHandler::getSlotDiffRenderer
+ */
+ public function testGetSlotDiffRenderer_hook() {
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'GetSlotDiffRenderer' => [],
+ ] );
+
+ // test that the hook handler takes precedence
+ $customDifferenceEngine = $this->getMockBuilder( DifferenceEngine::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $customContentHandler = $this->getMockBuilder( ContentHandler::class )
+ ->setConstructorArgs( [ 'foo', [] ] )
+ ->setMethods( [ 'createDifferenceEngine' ] )
+ ->getMockForAbstractClass();
+ $customContentHandler->expects( $this->any() )
+ ->method( 'createDifferenceEngine' )
+ ->willReturn( $customDifferenceEngine );
+ /** @var $customContentHandler ContentHandler */
+
+ $customSlotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $customContentHandler2 = $this->getMockBuilder( ContentHandler::class )
+ ->setConstructorArgs( [ 'bar', [] ] )
+ ->setMethods( [ 'createDifferenceEngine', 'getSlotDiffRendererInternal' ] )
+ ->getMockForAbstractClass();
+ $customContentHandler2->expects( $this->any() )
+ ->method( 'createDifferenceEngine' )
+ ->willReturn( $customDifferenceEngine );
+ $customContentHandler2->expects( $this->any() )
+ ->method( 'getSlotDiffRendererInternal' )
+ ->willReturn( $customSlotDiffRenderer );
+ /** @var $customContentHandler2 ContentHandler */
+
+ $customSlotDiffRenderer2 = $this->getMockBuilder( SlotDiffRenderer::class )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $this->setTemporaryHook( 'GetSlotDiffRenderer',
+ function ( $handler, &$slotDiffRenderer ) use ( $customSlotDiffRenderer2 ) {
+ $slotDiffRenderer = $customSlotDiffRenderer2;
+ } );
+
+ $slotDiffRenderer = $customContentHandler->getSlotDiffRenderer( RequestContext::getMain() );
+ $this->assertSame( $customSlotDiffRenderer2, $slotDiffRenderer );
+ $slotDiffRenderer = $customContentHandler2->getSlotDiffRenderer( RequestContext::getMain() );
+ $this->assertSame( $customSlotDiffRenderer2, $slotDiffRenderer );
+ }
+
}
--- /dev/null
+<?php
+
+class CustomDifferenceEngine extends DifferenceEngine {
+
+ public function __construct() {
+ parent::__construct();
+ }
+
+ public function generateContentDiffBody( Content $old, Content $new ) {
+ return $old->getNativeData() . '|' . $new->getNativeData();
+ }
+
+ public function showDiffStyle() {
+ $this->getOutput()->addModules( 'foo' );
+ }
+
+ public function getDiffBodyCacheKeyParams() {
+ $params = parent::getDiffBodyCacheKeyParams();
+ $params[] = 'foo';
+ return $params;
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @covers DifferenceEngineSlotDiffRenderer
+ */
+class DifferenceEngineSlotDiffRendererTest extends \PHPUnit\Framework\TestCase {
+
+ public function testGetDiff() {
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
+ $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+
+ $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
+ $this->assertEquals( 'xxx|yyy', $diff );
+
+ $diff = $slotDiffRenderer->getDiff( null, $newContent );
+ $this->assertEquals( '|yyy', $diff );
+
+ $diff = $slotDiffRenderer->getDiff( $oldContent, null );
+ $this->assertEquals( 'xxx|', $diff );
+ }
+
+ public function testAddModules() {
+ $output = $this->getMockBuilder( OutputPage::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'addModules' ] )
+ ->getMock();
+ $output->expects( $this->once() )
+ ->method( 'addModules' )
+ ->with( 'foo' );
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $slotDiffRenderer->addModules( $output );
+ }
+
+ public function testGetExtraCacheKeys() {
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
+ $this->assertSame( [ 'foo' ], $extraCacheKeys );
+ }
+
+}
<?php
+use MediaWiki\Storage\MutableRevisionRecord;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\SlotRecord;
use Wikimedia\TestingAccessWrapper;
/**
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] ],
];
}
$this->assertEquals( $expected, $diffEngine->addLocalisedTitleTooltips( $input ) );
}
+ /**
+ * @dataProvider provideGenerateContentDiffBody
+ */
+ public function testGenerateContentDiffBody(
+ Content $oldContent, Content $newContent, $expectedDiff
+ ) {
+ // Set $wgExternalDiffEngine to something bogus to try to force use of
+ // the PHP engine rather than wikidiff2.
+ $this->setMwGlobals( [
+ 'wgExternalDiffEngine' => '/dev/null',
+ ] );
+
+ $differenceEngine = new DifferenceEngine();
+ $diff = $differenceEngine->generateContentDiffBody( $oldContent, $newContent );
+ $this->assertSame( $expectedDiff, $this->getPlainDiff( $diff ) );
+ }
+
+ public function provideGenerateContentDiffBody() {
+ $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
+ 'testing-nontext' => DummyNonTextContentHandler::class,
+ ] );
+ $content1 = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
+ $content2 = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+
+ return [
+ 'self-diff' => [ $content1, $content1, '' ],
+ 'text diff' => [ $content1, $content2, '-xxx+yyy' ],
+ ];
+ }
+
+ public function testGenerateTextDiffBody() {
+ // Set $wgExternalDiffEngine to something bogus to try to force use of
+ // the PHP engine rather than wikidiff2.
+ $this->setMwGlobals( [
+ 'wgExternalDiffEngine' => '/dev/null',
+ ] );
+
+ $oldText = "aaa\nbbb\nccc";
+ $newText = "aaa\nxxx\nccc";
+ $expectedDiff = " aaa aaa\n-bbb+xxx\n ccc ccc";
+
+ $differenceEngine = new DifferenceEngine();
+ $diff = $differenceEngine->generateTextDiffBody( $oldText, $newText );
+ $this->assertSame( $expectedDiff, $this->getPlainDiff( $diff ) );
+ }
+
+ public function testSetContent() {
+ // Set $wgExternalDiffEngine to something bogus to try to force use of
+ // the PHP engine rather than wikidiff2.
+ $this->setMwGlobals( [
+ 'wgExternalDiffEngine' => '/dev/null',
+ ] );
+
+ $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
+ $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+
+ $differenceEngine = new DifferenceEngine();
+ $differenceEngine->setContent( $oldContent, $newContent );
+ $diff = $differenceEngine->getDiffBody();
+ $this->assertSame( "Line 1:\nLine 1:\n-xxx+yyy", $this->getPlainDiff( $diff ) );
+ }
+
+ public function testSetRevisions() {
+ $main1 = SlotRecord::newUnsaved( 'main',
+ ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT ) );
+ $main2 = SlotRecord::newUnsaved( 'main',
+ ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT ) );
+ $rev1 = $this->getRevisionRecord( $main1 );
+ $rev2 = $this->getRevisionRecord( $main2 );
+
+ $differenceEngine = new DifferenceEngine();
+ $differenceEngine->setRevisions( $rev1, $rev2 );
+ $this->assertSame( $rev1, $differenceEngine->getOldRevision() );
+ $this->assertSame( $rev2, $differenceEngine->getNewRevision() );
+ $this->assertSame( true, $differenceEngine->loadRevisionData() );
+ $this->assertSame( true, $differenceEngine->loadText() );
+
+ $differenceEngine->setRevisions( null, $rev2 );
+ $this->assertSame( null, $differenceEngine->getOldRevision() );
+ }
+
+ /**
+ * @dataProvider provideGetDiffBody
+ */
+ public function testGetDiffBody(
+ RevisionRecord $oldRevision = null, RevisionRecord $newRevision = null, $expectedDiff
+ ) {
+ // Set $wgExternalDiffEngine to something bogus to try to force use of
+ // the PHP engine rather than wikidiff2.
+ $this->setMwGlobals( [
+ 'wgExternalDiffEngine' => '/dev/null',
+ ] );
+
+ if ( $expectedDiff instanceof Exception ) {
+ $this->setExpectedException( get_class( $expectedDiff ), $expectedDiff->getMessage() );
+ }
+ $differenceEngine = new DifferenceEngine();
+ $differenceEngine->setRevisions( $oldRevision, $newRevision );
+ if ( $expectedDiff instanceof Exception ) {
+ return;
+ }
+
+ $diff = $differenceEngine->getDiffBody();
+ $this->assertSame( $expectedDiff, $this->getPlainDiff( $diff ) );
+ }
+
+ public function provideGetDiffBody() {
+ $main1 = SlotRecord::newUnsaved( 'main',
+ ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT ) );
+ $main2 = SlotRecord::newUnsaved( 'main',
+ ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT ) );
+ $slot1 = SlotRecord::newUnsaved( 'slot',
+ ContentHandler::makeContent( 'aaa', null, CONTENT_MODEL_TEXT ) );
+ $slot2 = SlotRecord::newUnsaved( 'slot',
+ ContentHandler::makeContent( 'bbb', null, CONTENT_MODEL_TEXT ) );
+
+ return [
+ 'revision vs. null' => [
+ null,
+ $this->getRevisionRecord( $main1, $slot1 ),
+ '',
+ ],
+ 'revision vs. itself' => [
+ $this->getRevisionRecord( $main1, $slot1 ),
+ $this->getRevisionRecord( $main1, $slot1 ),
+ '',
+ ],
+ 'different text in one slot' => [
+ $this->getRevisionRecord( $main1, $slot1 ),
+ $this->getRevisionRecord( $main1, $slot2 ),
+ "slotLine 1:\nLine 1:\n-aaa+bbb",
+ ],
+ 'different text in two slots' => [
+ $this->getRevisionRecord( $main1, $slot1 ),
+ $this->getRevisionRecord( $main2, $slot2 ),
+ "Line 1:\nLine 1:\n-xxx+yyy\nslotLine 1:\nLine 1:\n-aaa+bbb",
+ ],
+ 'new slot' => [
+ $this->getRevisionRecord( $main1 ),
+ $this->getRevisionRecord( $main1, $slot1 ),
+ "slotLine 1:\nLine 1:\n- +aaa",
+ ],
+ ];
+ }
+
+ public function testRecursion() {
+ // Set up a ContentHandler which will return a wrapped DifferenceEngine as
+ // SlotDiffRenderer, then pass it a content which uses the same ContentHandler.
+ // This tests the anti-recursion logic in DifferenceEngine::generateContentDiffBody.
+
+ $customDifferenceEngine = $this->getMockBuilder( DifferenceEngine::class )
+ ->enableProxyingToOriginalMethods()
+ ->getMock();
+ $customContentHandler = $this->getMockBuilder( ContentHandler::class )
+ ->setConstructorArgs( [ 'foo', [] ] )
+ ->setMethods( [ 'createDifferenceEngine' ] )
+ ->getMockForAbstractClass();
+ $customContentHandler->expects( $this->any() )
+ ->method( 'createDifferenceEngine' )
+ ->willReturn( $customDifferenceEngine );
+ /** @var $customContentHandler ContentHandler */
+ $customContent = $this->getMockBuilder( Content::class )
+ ->setMethods( [ 'getContentHandler' ] )
+ ->getMockForAbstractClass();
+ $customContent->expects( $this->any() )
+ ->method( 'getContentHandler' )
+ ->willReturn( $customContentHandler );
+ /** @var $customContent Content */
+ $customContent2 = clone $customContent;
+
+ $slotDiffRenderer = $customContentHandler->getSlotDiffRenderer( RequestContext::getMain() );
+ $this->setExpectedException( Exception::class,
+ ': could not maintain backwards compatibility. Please use a SlotDiffRenderer.' );
+ $slotDiffRenderer->getDiff( $customContent, $customContent2 );
+ }
+
+ /**
+ * Convert a HTML diff to a human-readable format and hopefully make the test less fragile.
+ * @param string diff
+ * @return string
+ */
+ private function getPlainDiff( $diff ) {
+ $replacements = [
+ html_entity_decode( ' ' ) => ' ',
+ html_entity_decode( '−' ) => '-',
+ ];
+ return str_replace( array_keys( $replacements ), array_values( $replacements ),
+ trim( strip_tags( $diff ), "\n" ) );
+ }
+
+ /**
+ * @param SlotRecord[] $slots
+ * @return MutableRevisionRecord
+ */
+ private function getRevisionRecord( ...$slots ) {
+ $title = Title::newFromText( 'Foo' );
+ $revision = new MutableRevisionRecord( $title );
+ foreach ( $slots as $slot ) {
+ $revision->setSlot( $slot );
+ }
+ return $revision;
+ }
+
}
--- /dev/null
+<?php
+
+/**
+ * @covers TextSlotDiffRenderer
+ */
+class TextSlotDiffRendererTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider provideGetDiff
+ * @param Content|null $oldContent
+ * @param Content|null $newContent
+ * @param string|Exception $expectedResult
+ * @throws Exception
+ */
+ public function testGetDiff(
+ Content $oldContent = null, Content $newContent = null, $expectedResult
+ ) {
+ if ( $expectedResult instanceof Exception ) {
+ $this->setExpectedException( get_class( $expectedResult ), $expectedResult->getMessage() );
+ }
+
+ $slotDiffRenderer = $this->getTextSlotDiffRenderer();
+ $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
+ if ( $expectedResult instanceof Exception ) {
+ return;
+ }
+ $plainDiff = $this->getPlainDiff( $diff );
+ $this->assertSame( $expectedResult, $plainDiff );
+ }
+
+ public function provideGetDiff() {
+ $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
+ 'testing' => DummyContentHandlerForTesting::class,
+ 'testing-nontext' => DummyNonTextContentHandler::class,
+ ] );
+
+ return [
+ 'same text' => [
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ "",
+ ],
+ 'different text' => [
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ $this->makeContent( "aaa\nxxx\nccc" ),
+ " aaa aaa\n-bbb+xxx\n ccc ccc",
+ ],
+ 'no right content' => [
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ null,
+ "-aaa+ \n-bbb \n-ccc ",
+ ],
+ 'no left content' => [
+ null,
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ "- +aaa\n +bbb\n +ccc",
+ ],
+ 'no content' => [
+ null,
+ null,
+ new InvalidArgumentException( '$oldContent and $newContent cannot both be null' ),
+ ],
+ 'non-text left content' => [
+ $this->makeContent( '', 'testing-nontext' ),
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ new InvalidArgumentException( 'TextSlotDiffRenderer does not handle DummyNonTextContent' ),
+ ],
+ 'non-text right content' => [
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ $this->makeContent( '', 'testing-nontext' ),
+ new InvalidArgumentException( 'TextSlotDiffRenderer does not handle DummyNonTextContent' ),
+ ],
+ ];
+ }
+
+ // no separate test for getTextDiff() as getDiff() is just a thin wrapper around it
+
+ /**
+ * @return TextSlotDiffRenderer
+ */
+ private function getTextSlotDiffRenderer() {
+ $slotDiffRenderer = new TextSlotDiffRenderer();
+ $slotDiffRenderer->setStatsdDataFactory( new NullStatsdDataFactory() );
+ $slotDiffRenderer->setLanguage( Language::factory( 'en' ) );
+ $slotDiffRenderer->setWikiDiff2MovedParagraphDetectionCutoff( 0 );
+ $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_PHP );
+ return $slotDiffRenderer;
+ }
+
+ /**
+ * Convert a HTML diff to a human-readable format and hopefully make the test less fragile.
+ * @param string diff
+ * @return string
+ */
+ private function getPlainDiff( $diff ) {
+ $replacements = [
+ html_entity_decode( ' ' ) => ' ',
+ html_entity_decode( '−' ) => '-',
+ ];
+ return str_replace( array_keys( $replacements ), array_values( $replacements ),
+ trim( strip_tags( $diff ), "\n" ) );
+ }
+
+ /**
+ * @param string $str
+ * @param string $model
+ * @return null|TextContent
+ */
+ private function makeContent( $str, $model = CONTENT_MODEL_TEXT ) {
+ return ContentHandler::makeContent( $str, null, $model );
+ }
+
+}
'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 ) );
+ }
+
+}
];
}
+ public function testWrapOutput() {
+ global $wgParser;
+ $title = Title::newFromText( 'foo' );
+ $po = new ParserOptions();
+ $wgParser->parse( 'Hello World', $title, $po );
+ $text = $wgParser->getOutput()->getText();
+
+ $this->assertContains( 'Hello World', $text );
+ $this->assertContains( '<div', $text );
+ $this->assertContains( 'class="mw-parser-output"', $text );
+ }
+
// @todo Add tests for cleanSig() / cleanSigInSig(), getSection(),
// replaceSection(), getPreloadText()
}
$this->assertArrayNotHasKey( 'foo', $properties );
}
+ /**
+ * @covers ParserOutput::getWrapperDivClass
+ * @covers ParserOutput::addWrapperDivClass
+ * @covers ParserOutput::clearWrapperDivClass
+ * @covers ParserOutput::getText
+ */
+ public function testWrapperDivClass() {
+ $po = new ParserOutput();
+
+ $po->setText( 'Kittens' );
+ $this->assertContains( 'Kittens', $po->getText() );
+ $this->assertNotContains( '<div', $po->getText() );
+ $this->assertSame( 'Kittens', $po->getRawText() );
+
+ $po->addWrapperDivClass( 'foo' );
+ $text = $po->getText();
+ $this->assertContains( 'Kittens', $text );
+ $this->assertContains( '<div', $text );
+ $this->assertContains( 'class="foo"', $text );
+
+ $po->addWrapperDivClass( 'bar' );
+ $text = $po->getText();
+ $this->assertContains( 'Kittens', $text );
+ $this->assertContains( '<div', $text );
+ $this->assertContains( 'class="foo bar"', $text );
+
+ $po->addWrapperDivClass( 'bar' ); // second time does nothing, no "foo bar bar".
+ $text = $po->getText( [ 'unwrap' => true ] );
+ $this->assertContains( 'Kittens', $text );
+ $this->assertNotContains( '<div', $text );
+ $this->assertNotContains( 'class="foo bar"', $text );
+
+ $text = $po->getText( [ 'wrapperDivClass' => '' ] );
+ $this->assertContains( 'Kittens', $text );
+ $this->assertNotContains( '<div', $text );
+ $this->assertNotContains( 'class="foo bar"', $text );
+
+ $text = $po->getText( [ 'wrapperDivClass' => 'xyzzy' ] );
+ $this->assertContains( 'Kittens', $text );
+ $this->assertContains( '<div', $text );
+ $this->assertContains( 'class="xyzzy"', $text );
+ $this->assertNotContains( 'class="foo bar"', $text );
+
+ $text = $po->getRawText();
+ $this->assertSame( 'Kittens', $text );
+
+ $po->clearWrapperDivClass();
+ $text = $po->getText();
+ $this->assertContains( 'Kittens', $text );
+ $this->assertNotContains( '<div', $text );
+ $this->assertNotContains( 'class="foo bar"', $text );
+ }
+
/**
* @covers ParserOutput::getText
* @dataProvider provideGetText
public static function provideGetText() {
// phpcs:disable Generic.Files.LineLength
$text = <<<EOF
-<div class="mw-parser-output"><p>Test document.
+<p>Test document.
</p>
<mw:toc><div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><mw:editsection page="Test Page" section="4">Section 3</mw:editsection></h2>
<p>Three
-</p></div>
+</p>
EOF;
$dedupText = <<<EOF
return [
'No options' => [
[], $text, <<<EOF
-<div class="mw-parser-output"><p>Test document.
+<p>Test document.
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>Three
-</p></div>
+</p>
EOF
],
'Disable section edit links' => [
[ 'enableSectionEditLinks' => false ], $text, <<<EOF
-<div class="mw-parser-output"><p>Test document.
+<p>Test document.
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
<ul>
</p>
<h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
<p>Three
-</p></div>
+</p>
EOF
],
- 'Disable TOC' => [
- [ 'allowTOC' => false ], $text, <<<EOF
+ 'Disable TOC, but wrap' => [
+ [ 'allowTOC' => false, 'wrapperDivClass' => 'mw-parser-output' ], $text, <<<EOF
<div class="mw-parser-output"><p>Test document.
</p>
<p>Three
</p></div>
EOF
- ],
- 'Unwrap text' => [
- [ 'unwrap' => true ], $text, <<<EOF
-<p>Test document.
-</p>
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
-<ul>
-<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
-<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
-<ul>
-<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
-</ul>
-</li>
-<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
-</ul>
-</div>
-
-<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<p>One
-</p>
-<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<p>Two
-</p>
-<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
-<p>Two point one
-</p>
-<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<p>Three
-</p>
-EOF
- ],
- 'Unwrap without a mw-parser-output wrapper' => [
- [ 'unwrap' => true ], '<div class="foobar">Content</div>', '<div class="foobar">Content</div>'
- ],
- 'Unwrap with extra comment at end' => [
- [ 'unwrap' => true ], '<div class="mw-parser-output"><p>Test document.</p></div>
-<!-- Saved in parser cache... -->', '<p>Test document.</p>
-<!-- Saved in parser cache... -->'
],
'Style deduplication' => [
[], $dedupText, <<<EOF
<?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',
+ ],
+ ];
+ }
}
* Test specifications by Alexandre "ialex" Emsenhuber.
* @todo give this test a real name explaining what is being tested here
*/
- public function testBug41337() {
+ public function testT43337() {
// Set a low limit
$this->setMwGlobals( 'wgMaxSigChars', 2 );
// getGenderCache() provides a mock that considers first
// names ending in "a" to be female.
[ NS_USER, 'Lisa_Müller', '', 'de', 'Benutzerin:Lisa Müller' ],
- [ 1000000, 'Invalid_namespace', '', 'en', ':Invalid namespace' ],
+ [ 1000000, 'Invalid_namespace', '', 'en', 'Special:Badtitle/NS1000000:Invalid namespace' ],
];
}
[ NS_MAIN, 'Remote_page', '', 'remotetestiw', 'en', 'remotetestiw:Remote_page' ],
// non-existent namespace
- [ 10000000, 'Foobar', '', '', 'en', ':Foobar' ],
+ [ 10000000, 'Foobar', '', '', 'en', 'Special:Badtitle/NS10000000:Foobar' ],
];
}
/**
* @todo give this test a real name explaining what is being tested here
*/
- public function testBug29408() {
+ public function testT31408() {
$this->setMwGlobals( 'wgUser', self::$users['uploader']->getUser() );
$repo = RepoGroup::singleton()->getLocalRepo();
],
[ 22 => true ],
],
- 'change in categories' => [
- __DIR__ . "/../data/categoriesrdf/change.sparql",
+ 'edit category' => [
+ __DIR__ . "/../data/categoriesrdf/edit.sparql",
'getChangedCatsIterator',
- 'handleChanges',
+ 'handleEdits',
[
(object)[
'rc_title' => 'Changed category',
'rc_title' => 'Changed again',
'rc_cur_id' => 30,
'pp_propname' => null,
- 'cat_pages' => 10,
+ 'cat_pages' => 12,
'cat_subcats' => 2,
'cat_files' => 1,
],
],
[ 31 => true ],
],
-
+ // TODO: not sure how to test categorization changes, it uses the database select...
];
}
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' );
} );
- QUnit.test( 'stickyRandomId', function ( assert ) {
- var result = mw.user.stickyRandomId(),
- result2 = mw.user.stickyRandomId();
+ QUnit.test( 'getPageviewToken', function ( assert ) {
+ 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' );
} );