From: jenkins-bot Date: Wed, 22 Aug 2018 05:01:43 +0000 (+0000) Subject: Merge "rdbms: fix IDEA warnings in DatabaseMssql.php" X-Git-Tag: 1.34.0-rc.0~4339 X-Git-Url: http://git.cyclocoop.org/%7B%24admin_url%7Dmes_infos.php?a=commitdiff_plain;h=da748c3a91b06be4d938570ef86ec05635e7aed3;hp=2b5e59fad1c9a501a189936028275f44b14147b0;p=lhc%2Fweb%2Fwiklou.git Merge "rdbms: fix IDEA warnings in DatabaseMssql.php" --- diff --git a/RELEASE-NOTES-1.32 b/RELEASE-NOTES-1.32 index 158fbe2648..0ad2e41b65 100644 --- a/RELEASE-NOTES-1.32 +++ b/RELEASE-NOTES-1.32 @@ -37,6 +37,9 @@ production. 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`. === New features in 1.32 === * (T112474) Generalized the ResourceLoader mechanism for overriding modules @@ -68,6 +71,10 @@ production. 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. === External library changes in 1.32 === * … @@ -340,12 +347,20 @@ because of Phabricator reports. 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. @@ -360,6 +375,8 @@ because of Phabricator reports. * 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. === Other changes in 1.32 === * (T198811) The following tables have had their UNIQUE indexes turned into diff --git a/autoload.php b/autoload.php index a372dd19f0..e960f42274 100644 --- a/autoload.php +++ b/autoload.php @@ -12,6 +12,7 @@ $wgAutoloadLocalClasses = [ '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', @@ -405,6 +406,7 @@ $wgAutoloadLocalClasses = [ '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', @@ -842,6 +844,7 @@ $wgAutoloadLocalClasses = [ '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', @@ -1343,6 +1346,7 @@ $wgAutoloadLocalClasses = [ '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', @@ -1475,6 +1479,7 @@ $wgAutoloadLocalClasses = [ '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', diff --git a/docs/hooks.txt b/docs/hooks.txt index 219c51f30e..9cb22d23ed 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -1670,15 +1670,13 @@ $title: Title object that we need to get a sortkey for &$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. @@ -1785,6 +1783,12 @@ $relativeTo: MWTimestamp object of the relative (user-adjusted) timestamp $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 diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 736e31911d..fdac10a53e 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -1646,13 +1646,6 @@ $wgEmergencyContact = false; */ $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. * @@ -3799,16 +3792,6 @@ $wgResourceLoaderMaxQueryLength = false; */ $wgResourceLoaderValidateJS = true; -/** - * If set to true, statically-sourced (file-backed) JavaScript resources will - * be parsed for validity before being bundled up into ResourceLoader modules. - * - * 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. - */ -$wgResourceLoaderValidateStaticJS = false; - /** * Whether ResourceLoader should attempt to persist modules in localStorage on * browsers that support the Web Storage API. diff --git a/includes/Html.php b/includes/Html.php index 32375c1f57..dba4c67a72 100644 --- a/includes/Html.php +++ b/includes/Html.php @@ -704,7 +704,7 @@ class Html { * @return string of HTML representing a box. */ private static function messageBox( $html, $className, $heading = '' ) { - if ( $heading ) { + if ( $heading !== '' ) { $html = self::element( 'h2', [], $heading ) . $html; } return self::rawElement( 'div', [ 'class' => $className ], $html ); diff --git a/includes/Title.php b/includes/Title.php index e74824c9b8..c919b18856 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -134,8 +134,15 @@ class Title implements LinkTarget { /** @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; @@ -1669,12 +1676,12 @@ class Title implements LinkTarget { * @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; } /** diff --git a/includes/api/i18n/zh-hant.json b/includes/api/i18n/zh-hant.json index 1cced41bdc..1e590c9bd7 100644 --- a/includes/api/i18n/zh-hant.json +++ b/includes/api/i18n/zh-hant.json @@ -41,9 +41,11 @@ "apihelp-block-param-allowusertalk": "允許使用者編輯自己的對話頁面 (依據 [[mw:Special:MyLanguage/Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]] 的設定)。", "apihelp-block-param-reblock": "若使用者已被封鎖,覆寫既有的封鎖設定值。", "apihelp-block-param-watchuser": "監視使用者或 IP 位址的使用者頁面與對話頁面。", + "apihelp-block-param-tags": "在封鎖日誌裡更改套用到項目的標籤。", "apihelp-block-example-ip-simple": "封鎖 IP 位址 192.0.2.5 三天,原因為 First strike。", "apihelp-block-example-user-complex": "永久封鎖 IP 位址 Vandal,原因為 Vandalism。", "apihelp-changeauthenticationdata-summary": "為目前使用者變更身分核對資料。", + "apihelp-changeauthenticationdata-example-password": "嘗試更改目前使用者的密碼至 ExamplePassword。", "apihelp-checktoken-summary": "檢查來自 [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] 的密鑰有效性。", "apihelp-checktoken-param-type": "要測試的密鑰類型。", "apihelp-checktoken-param-token": "要測試的密鑰。", @@ -131,7 +133,9 @@ "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": "隱藏匿名使用者做的變更。", @@ -168,6 +172,7 @@ "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": "合併歷史的原因。", @@ -201,6 +206,7 @@ "apihelp-paraminfo-param-helpformat": "說明字串的格式。", "apihelp-parse-param-summary": "解析摘要。", "apihelp-parse-param-pageid": "解析此頁面的內容。覆蓋 $1page。", + "apihelp-parse-param-redirects": "若 $1page 或者 $1pageid 被設定成重新導向,則解析它。", "apihelp-parse-param-prop": "要取得的資訊部份:", "apihelp-parse-paramvalue-prop-headhtml": "取得頁面已解析的 <head>。", "apihelp-parse-param-disablepp": "請改用$1disablelimitreport。", @@ -229,6 +235,7 @@ "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": "列舉所有分類。", @@ -252,27 +259,39 @@ "apihelp-query+alldeletedrevisions-param-user": "此列出由該使用者作出的修訂。", "apihelp-query+alldeletedrevisions-param-excludeuser": "不要列出由該使用者作出的修訂。", "apihelp-query+alldeletedrevisions-param-namespace": "僅列出此命名空間的頁面。", + "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 類型,例如:image/jpeg。", "apihelp-query+allimages-param-limit": "要回傳的圖片總數。", + "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-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": "顯示以 ipb- 起始的訊息。", + "apihelp-query+allmessages-example-de": "顯示在德語裡的 august 與 mainpage 訊息。", "apihelp-query+allpages-param-from": "起始列舉的頁面標題。", "apihelp-query+allpages-param-to": "終止列舉的頁面標題。", "apihelp-query+allpages-param-prefix": "搜尋以此值為開頭的所有頁面標題。", @@ -292,6 +311,7 @@ "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": "起始列舉的時間戳記。", @@ -321,6 +341,7 @@ "apihelp-query+backlinks-summary": "找出連結至指定頁面的所有頁面。", "apihelp-query+backlinks-param-namespace": "要列舉的命名空間。", "apihelp-query+backlinks-param-dir": "列出時所採用的方向。", + "apihelp-query+backlinks-example-simple": "顯示至 Main page 的連結。", "apihelp-query+blocks-summary": "列出所有被封鎖使用者與 IP 位址。", "apihelp-query+blocks-param-start": "起始列舉的時間戳記。", "apihelp-query+blocks-param-end": "終止列舉的時間戳記。", @@ -332,16 +353,19 @@ "apihelp-query+blocks-paramvalue-prop-byid": "添加進行封鎖中的使用者之使用者 ID。", "apihelp-query+blocks-example-simple": "列出封鎖。", "apihelp-query+blocks-example-users": "列出使用者 Alice 與 Bob 的封鎖。", + "apihelp-query+categories-summary": "列出頁面隸屬的所有分類。", "apihelp-query+categories-param-limit": "要回傳的分類數量。", "apihelp-query+categoryinfo-summary": "回傳有關指定分類的資訊。", "apihelp-query+categorymembers-summary": "在指定的分類中列出所有頁面。", "apihelp-query+categorymembers-paramvalue-prop-ids": "添加頁面 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": "取得在 Category:Physics 裡前 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": "列出已刪除的修訂。", @@ -363,17 +387,27 @@ "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": "各頁面的標題。", @@ -394,13 +428,16 @@ "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-prop": "要取得的屬性。", "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-limit": "要回傳的頁面總數。", "apihelp-query+langbacklinks-param-prop": "要取得的屬性。", + "apihelp-query+langbacklinks-param-dir": "列出時所採用的方向。", "apihelp-query+langlinks-summary": "回傳指定頁面的所有跨語言連結。", "apihelp-query+langlinks-param-limit": "要回傳的 langlinks 數量。", "apihelp-query+langlinks-paramvalue-prop-url": "添加完整的 URL。", @@ -409,8 +446,10 @@ "apihelp-query+links-summary": "回傳指定頁面的所有連結。", "apihelp-query+links-param-limit": "要回傳的連結數量。", "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": "要取得的屬性。", @@ -418,14 +457,18 @@ "apihelp-query+logevents-param-end": "結束列舉的時間戳記。", "apihelp-query+logevents-param-limit": "要回傳的事件項目總數。", "apihelp-query+pagepropnames-param-limit": "回傳的名稱數量上限。", + "apihelp-query+pagepropnames-example-simple": "取得前 10 個屬性名稱。", "apihelp-query+pageswithprop-paramvalue-prop-ids": "添加頁面 ID。", "apihelp-query+pageswithprop-param-limit": "回傳的頁面數量上限。", "apihelp-query+prefixsearch-param-search": "搜尋字串。", "apihelp-query+prefixsearch-param-namespace": "搜尋的命名空間。若 $1search 以有效的命名空間前綴為開頭則會被忽略。", "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": "取得隨機頁面集合", @@ -448,6 +491,7 @@ "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+revisions-summary": "取得修訂的資訊。", "apihelp-query+revisions-example-content": "取得用於標題 API 與 Main Page 最新修訂內容的資料。", @@ -469,6 +513,7 @@ "apihelp-query+search-paramvalue-prop-hasrelated": "已忽略", "apihelp-query+search-param-limit": "要回傳的頁面總數。", "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 前綴的清單。", @@ -499,6 +544,8 @@ "apihelp-query+usercontribs-paramvalue-prop-comment": "添加編輯的註釋。", "apihelp-query+usercontribs-paramvalue-prop-parsedcomment": "添加編輯的已解析註解。", "apihelp-query+usercontribs-paramvalue-prop-size": "添加編輯的新大小。", + "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": "添加使用者的註冊日期。", diff --git a/includes/changetags/ChangeTags.php b/includes/changetags/ChangeTags.php index b5bf488acb..dd29c100e8 100644 --- a/includes/changetags/ChangeTags.php +++ b/includes/changetags/ChangeTags.php @@ -201,9 +201,8 @@ class ChangeTags { } $taglessDesc = Sanitizer::stripAllTags( $originalDesc->parse() ); - $escapedDesc = Sanitizer::escapeHtmlAllowEntities( $taglessDesc ); - return $context->getLanguage()->truncateForVisual( $escapedDesc, $length ); + return $context->getLanguage()->truncateForVisual( $taglessDesc, $length ); } /** @@ -1184,7 +1183,7 @@ class ChangeTags { * 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 diff --git a/includes/content/ContentHandler.php b/includes/content/ContentHandler.php index 004e38a5ef..b3286a9a29 100644 --- a/includes/content/ContentHandler.php +++ b/includes/content/ContentHandler.php @@ -1,5 +1,6 @@ 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' )->notice( + '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; } /** diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php index 49777627af..876b9bb9fa 100644 --- a/includes/db/DatabaseOracle.php +++ b/includes/db/DatabaseOracle.php @@ -81,15 +81,6 @@ class DatabaseOracle extends Database { 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' ) ) { @@ -173,7 +164,7 @@ class DatabaseOracle extends Database { $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; } /** diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php index 5138655763..2ceda216d0 100644 --- a/includes/diff/DifferenceEngine.php +++ b/includes/diff/DifferenceEngine.php @@ -20,12 +20,30 @@ * @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 { @@ -48,26 +66,55 @@ class DifferenceEngine extends ContextSource { 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. + * Null when the old revision does not exist; this can happen when using + * diff=prev on the first revision. + * Since 1.32 public access is deprecated. + * @var Revision|null + */ + protected $mOldRev; - /** @var Language */ - protected $mDiffLang; + /** + * New revision (right pane). + * Note that this might be an unsaved revision (e.g. for edit preview). + * Null only in case of load failure; diff methods will just return an error message in that case. + * 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; + /** + * @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 Revision|null */ - public $mNewRev; + /** @var Language */ + protected $mDiffLang; /** @var bool Have the revisions IDs been loaded */ private $mRevisionsIdsLoaded = false; @@ -80,7 +127,10 @@ class DifferenceEngine extends ContextSource { /** * 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; @@ -109,6 +159,17 @@ class DifferenceEngine extends ContextSource { /** @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; + /**#@-*/ /** @@ -124,6 +185,8 @@ class DifferenceEngine extends ContextSource { ) { $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__ ); @@ -145,6 +208,81 @@ class DifferenceEngine extends ContextSource { } /** + * @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, 'new' => SlotRecord ], ... ] + */ + protected function getSlotContents() { + if ( $this->isContentOverridden ) { + return [ + 'main' => [ + 'old' => $this->mOldContent, + 'new' => $this->mNewContent, + ] + ]; + } + + $oldRev = $this->mOldRev->getRevisionRecord(); + $newRev = $this->mNewRev->getRevisionRecord(); + // The order here will determine the visual order of the diff. The current logic is + // changed first, then added, then deleted. This is ad hoc and should not be relied on + // - in the future we may want the ordering to depend on the page type. + $roles = array_merge( $newRev->getSlotRoles(), $oldRev->getSlotRoles() ); + $oldSlots = $oldRev->getSlots()->getSlots(); + $newSlots = $newRev->getSlots()->getSlots(); + + $slots = []; + 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 ) { @@ -190,6 +328,25 @@ class DifferenceEngine extends ContextSource { 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. @@ -280,8 +437,11 @@ class DifferenceEngine extends ContextSource { } $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 ) ); } @@ -311,7 +471,9 @@ class DifferenceEngine extends ContextSource { # 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 @@ -319,7 +481,10 @@ class DifferenceEngine extends ContextSource { } 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 { @@ -329,7 +494,7 @@ class DifferenceEngine extends ContextSource { $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 ) { @@ -356,7 +521,7 @@ class DifferenceEngine extends ContextSource { } # 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(), @@ -409,7 +574,7 @@ class DifferenceEngine extends ContextSource { # 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(), @@ -517,7 +682,7 @@ class DifferenceEngine extends ContextSource { 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 = ' [' . @@ -553,7 +718,7 @@ class DifferenceEngine extends ContextSource { // 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 ) @@ -626,6 +791,12 @@ class DifferenceEngine extends ContextSource { # 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 ); @@ -714,7 +885,12 @@ class DifferenceEngine extends ContextSource { * 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() ); + } + } } /** @@ -751,25 +927,28 @@ class DifferenceEngine extends ContextSource { 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(); @@ -800,7 +979,20 @@ class DifferenceEngine extends ContextSource { 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; @@ -822,6 +1014,21 @@ class DifferenceEngine extends ContextSource { return $difftext; } + /** + * 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=&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. * @@ -867,98 +1074,112 @@ class DifferenceEngine extends ContextSource { $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. @@ -989,91 +1210,23 @@ class DifferenceEngine extends ContextSource { * * @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(); } /** @@ -1100,6 +1253,17 @@ class DifferenceEngine extends ContextSource { " -->\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 * @@ -1170,10 +1334,12 @@ class DifferenceEngine extends ContextSource { * @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 ''; } @@ -1353,6 +1519,7 @@ class DifferenceEngine extends ContextSource { * @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; @@ -1361,6 +1528,38 @@ class DifferenceEngine extends ContextSource { $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->mOldRev = $this->mOldid = $this->mOldPage = null; + } + $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; } /** @@ -1469,7 +1668,11 @@ class DifferenceEngine extends ContextSource { // 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; @@ -1491,8 +1694,10 @@ class DifferenceEngine extends ContextSource { 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 diff --git a/includes/diff/DifferenceEngineSlotDiffRenderer.php b/includes/diff/DifferenceEngineSlotDiffRenderer.php new file mode 100644 index 0000000000..0c632b97ec --- /dev/null +++ b/includes/diff/DifferenceEngineSlotDiffRenderer.php @@ -0,0 +1,79 @@ +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(); + } + +} diff --git a/includes/diff/SlotDiffRenderer.php b/includes/diff/SlotDiffRenderer.php new file mode 100644 index 0000000000..f20b4169db --- /dev/null +++ b/includes/diff/SlotDiffRenderer.php @@ -0,0 +1,65 @@ +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', '>=' ) + ) { + $text = wikidiff2_do_diff( + $oldText, + $newText, + 2, + $this->wikiDiff2MovedParagraphDetectionCutoff + ); + } else { + // Don't pass the 4th parameter for compatibility with older versions of wikidiff2 + $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 ); + } + +} diff --git a/includes/libs/rdbms/database/DBConnRef.php b/includes/libs/rdbms/database/DBConnRef.php index eba1657f29..0de90c9d04 100644 --- a/includes/libs/rdbms/database/DBConnRef.php +++ b/includes/libs/rdbms/database/DBConnRef.php @@ -178,10 +178,6 @@ class DBConnRef implements IDatabase { 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() ); } diff --git a/includes/libs/rdbms/database/Database.php b/includes/libs/rdbms/database/Database.php index e35e0827b0..88b6d2079f 100644 --- a/includes/libs/rdbms/database/Database.php +++ b/includes/libs/rdbms/database/Database.php @@ -373,6 +373,18 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware } } + /** + * 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 * diff --git a/includes/libs/rdbms/database/DatabaseMssql.php b/includes/libs/rdbms/database/DatabaseMssql.php index c55e04a66b..1246e4463f 100644 --- a/includes/libs/rdbms/database/DatabaseMssql.php +++ b/includes/libs/rdbms/database/DatabaseMssql.php @@ -77,16 +77,7 @@ class DatabaseMssql extends Database { 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( @@ -130,7 +121,7 @@ class DatabaseMssql extends Database { $this->opened = true; - return $this->conn; + return (bool)$this->conn; } /** diff --git a/includes/libs/rdbms/database/DatabaseMysqlBase.php b/includes/libs/rdbms/database/DatabaseMysqlBase.php index 57fab54936..0f575516b4 100644 --- a/includes/libs/rdbms/database/DatabaseMysqlBase.php +++ b/includes/libs/rdbms/database/DatabaseMysqlBase.php @@ -120,15 +120,7 @@ abstract class DatabaseMysqlBase extends Database { 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(); diff --git a/includes/libs/rdbms/database/DatabasePostgres.php b/includes/libs/rdbms/database/DatabasePostgres.php index a959d72ba8..3c2f145656 100644 --- a/includes/libs/rdbms/database/DatabasePostgres.php +++ b/includes/libs/rdbms/database/DatabasePostgres.php @@ -86,7 +86,7 @@ class DatabasePostgres extends Database { 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( diff --git a/includes/libs/rdbms/database/DatabaseSqlite.php b/includes/libs/rdbms/database/DatabaseSqlite.php index 25fbba09e5..1b9675add6 100644 --- a/includes/libs/rdbms/database/DatabaseSqlite.php +++ b/includes/libs/rdbms/database/DatabaseSqlite.php @@ -155,24 +155,14 @@ class DatabaseSqlite extends Database { 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; diff --git a/includes/libs/rdbms/database/IDatabase.php b/includes/libs/rdbms/database/IDatabase.php index 7da259d9f0..f97db3a7ca 100644 --- a/includes/libs/rdbms/database/IDatabase.php +++ b/includes/libs/rdbms/database/IDatabase.php @@ -370,18 +370,6 @@ interface IDatabase { */ 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 diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php index 6ee030e0e8..42bd66a0e8 100644 --- a/includes/resourceloader/ResourceLoaderFileModule.php +++ b/includes/resourceloader/ResourceLoaderFileModule.php @@ -460,9 +460,6 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { 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; } @@ -807,12 +804,6 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { 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; diff --git a/includes/search/SearchEngine.php b/includes/search/SearchEngine.php index 965a413508..ad9f934730 100644 --- a/includes/search/SearchEngine.php +++ b/includes/search/SearchEngine.php @@ -32,6 +32,8 @@ use MediaWiki\MediaWikiServices; * @ingroup Search */ abstract class SearchEngine { + const DEFAULT_SORT = 'relevance'; + /** @var string */ public $prefix = ''; @@ -49,7 +51,7 @@ abstract class SearchEngine { /** @var bool */ protected $showSuggestion = true; - private $sort = 'relevance'; + private $sort = self::DEFAULT_SORT; /** @var array Feature values */ protected $features = []; @@ -346,13 +348,13 @@ abstract class SearchEngine { /** * 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 ]; } /** diff --git a/includes/skins/Skin.php b/includes/skins/Skin.php index 43b3ec1cad..c603f2f3f3 100644 --- a/includes/skins/Skin.php +++ b/includes/skins/Skin.php @@ -1472,7 +1472,7 @@ abstract class Skin extends ContextSource { $uTalkTitle, $this->msg( 'newmessageslinkplural' )->params( $plural )->escaped(), [], - [ 'redirect' => 'no' ] + $uTalkTitle->isRedirect() ? [ 'redirect' => 'no' ] : [] ); $newMessagesDiffLink = Linker::linkKnown( diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php index 0069ea1bbe..464be4faca 100644 --- a/includes/specials/SpecialMovepage.php +++ b/includes/specials/SpecialMovepage.php @@ -356,8 +356,8 @@ class MovePageForm extends UnlistedSpecialPage { [ 'label' => $this->msg( 'movetalk' )->text(), 'help' => new OOUI\HtmlSnippet( $this->msg( 'movepagetalktext' )->parseAsBlock() ), + 'helpInline' => true, 'align' => 'inline', - 'infusable' => true, 'id' => 'wpMovetalk-field', ] ); diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index 513d2763c1..86dcb721f8 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -76,6 +76,11 @@ class SpecialSearch extends SpecialPage { */ protected $fulltext; + /** + * @var string + */ + protected $sort; + /** * @var bool */ @@ -198,6 +203,11 @@ class SpecialSearch extends SpecialPage { $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 @@ -301,6 +311,7 @@ class SpecialSearch extends SpecialPage { $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 ] ); diff --git a/includes/title/MediaWikiTitleCodec.php b/includes/title/MediaWikiTitleCodec.php index 15f8ff0997..f6a4c06522 100644 --- a/includes/title/MediaWikiTitleCodec.php +++ b/includes/title/MediaWikiTitleCodec.php @@ -79,7 +79,7 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser { * @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() && @@ -112,29 +112,30 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser { * @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; } /** @@ -185,12 +186,16 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser { * @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; } /** @@ -200,28 +205,12 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser { * @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() + ), ' ', '_' ); } /** diff --git a/includes/title/TitleValue.php b/includes/title/TitleValue.php index 43a399ab1c..698bc4f8be 100644 --- a/includes/title/TitleValue.php +++ b/includes/title/TitleValue.php @@ -59,6 +59,16 @@ class TitleValue implements LinkTarget { */ 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. * diff --git a/includes/user/CentralIdLookup.php b/includes/user/CentralIdLookup.php index 2a86f4a891..1d7f380822 100644 --- a/includes/user/CentralIdLookup.php +++ b/includes/user/CentralIdLookup.php @@ -67,6 +67,7 @@ abstract class CentralIdLookup implements IDBAccessObject { /** * Reset internal cache for unit testing + * @codeCoverageIgnore */ public static function resetCache() { if ( !defined( 'MW_PHPUNIT_TEST' ) ) { diff --git a/includes/widget/search/InterwikiSearchResultSetWidget.php b/includes/widget/search/InterwikiSearchResultSetWidget.php index b4e3414857..79380def75 100644 --- a/includes/widget/search/InterwikiSearchResultSetWidget.php +++ b/includes/widget/search/InterwikiSearchResultSetWidget.php @@ -65,12 +65,10 @@ class InterwikiSearchResultSetWidget implements SearchResultSetWidget { $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(); } } diff --git a/languages/data/ZhConversion.php b/languages/data/ZhConversion.php index c773b48a38..617545dbbc 100644 --- a/languages/data/ZhConversion.php +++ b/languages/data/ZhConversion.php @@ -3215,7 +3215,10 @@ public static $zh2Hant = [ '9只' => '9隻', '9余' => '9餘', '·范' => '·范', +'’m' => '’m', +'’re' => '’re', '’s' => '’s', +'’t' => '’t', '、面点' => '、麵點', '。个中' => '。箇中', '〇周后' => '〇周後', @@ -4025,6 +4028,7 @@ public static $zh2Hant = [ '债累累' => '債纍纍', '傻里傻气' => '傻裡傻氣', '仅余' => '僅餘', +'像赞' => '像讚', '仆人' => '僕人', '仆使' => '僕使', '仆仆' => '僕僕', @@ -4302,8 +4306,6 @@ public static $zh2Hant = [ '劉佳怜' => '劉佳怜', '刘芸后' => '劉芸后', '劉芸后' => '劉芸后', -'力拼' => '力拚', -'力拼众敌' => '力拼眾敵', '力争上游' => '力爭上遊', '功勋' => '功勳', '加氢精制' => '加氫精制', @@ -4319,6 +4321,7 @@ public static $zh2Hant = [ '勾干' => '勾幹', '勾心斗角' => '勾心鬥角', '勾魂荡魄' => '勾魂蕩魄', +'包干' => '包幹', '包括' => '包括', '包准' => '包準', '包谷' => '包穀', @@ -4752,6 +4755,7 @@ public static $zh2Hant = [ '寿面' => '壽麵', '夏于乔' => '夏于喬', '夏于喬' => '夏于喬', +'夏后氏' => '夏后氏', '夏历' => '夏曆', '夏历史' => '夏歷史', '夏游' => '夏遊', @@ -5579,7 +5583,6 @@ public static $zh2Hant = [ '恋恋不舍' => '戀戀不捨', '成于思' => '成於思', '戬谷' => '戩穀', -'截发' => '截髮', '战天斗地' => '戰天鬥地', '战栗' => '戰慄', '战斗' => '戰鬥', @@ -5636,7 +5639,6 @@ public static $zh2Hant = [ '打卡钟' => '打卡鐘', '打吨' => '打吨', '打干' => '打幹', -'打拼' => '打拚', '打断发' => '打斷發', '打卤' => '打滷', '打谷' => '打穀', @@ -5693,20 +5695,13 @@ public static $zh2Hant = [ '拔须' => '拔鬚', '拗别' => '拗彆', '拙朴' => '拙樸', -'拼却' => '拚卻', -'拼命' => '拚命', -'拼舍' => '拚捨', -'拼死' => '拚死', -'拼生尽死' => '拚生盡死', -'拼绝' => '拚絕', -'拼老命' => '拚老命', -'拼斗' => '拚鬥', +'拚舍' => '拚捨', '拜托' => '拜託', '括发' => '括髮', '拭干' => '拭乾', '拮据' => '拮据', '拳局' => '拳跼', -'拼死拼活' => '拼死拼活', +'拼斗' => '拼鬥', '拾沈' => '拾瀋', '拿准' => '拿準', '拿破仑' => '拿破崙', @@ -5936,6 +5931,7 @@ public static $zh2Hant = [ '摄制' => '攝製', '支干' => '支幹', '支配欲' => '支配慾', +'收回' => '收回', '收获' => '收穫', '改制成' => '改制成', '改征' => '改徵', @@ -6569,7 +6565,6 @@ public static $zh2Hant = [ '卤鸭' => '滷鴨', '卤鹅' => '滷鵝', '卤面' => '滷麵', -'满拼自尽' => '滿拚自盡', '满满当当' => '滿滿當當', '满头洋发' => '滿頭洋髮', '漂荡' => '漂蕩', @@ -6641,7 +6636,6 @@ public static $zh2Hant = [ '火并非' => '火並非', '火并' => '火併', '火山里' => '火山裡', -'火拼' => '火拚', '火折子' => '火摺子', '火签' => '火籤', '灰蒙' => '灰濛', @@ -7473,6 +7467,7 @@ public static $zh2Hant = [ '舌干唇焦' => '舌乾唇焦', '舍入口' => '舍入口', '舒卷' => '舒捲', +'舞台风格' => '舞台風格', '舞后' => '舞后', '航海历' => '航海曆', '航海历史' => '航海歷史', @@ -7678,7 +7673,6 @@ public static $zh2Hant = [ '蟻后' => '蟻后', '蚃干' => '蠁幹', '蛮干' => '蠻幹', -'血拼' => '血拚', '血余' => '血餘', '行事历' => '行事曆', '行事历史' => '行事歷史', @@ -8621,6 +8615,7 @@ public static $zh2Hant = [ '鉴赏' => '鑑賞', '長几' => '長几', '长几' => '長几', +'长得丑' => '長得醜', '长历' => '長曆', '长历史' => '長歷史', '长发公主' => '長髮公主', @@ -9812,7 +9807,6 @@ public static $zh2Hans = [ '嗩' => '唢', '嗶' => '哔', '嗹' => '𪡏', -'嘅' => '慨', '嘆' => '叹', '嘍' => '喽', '嘑' => '呼', @@ -13575,6 +13569,7 @@ public static $zh2Hans = [ '乾陵' => '乾陵', '乾隆' => '乾隆', '乾音' => '乾音', +'乾顺' => '乾顺', '乾顧' => '乾顾', '乾顾' => '乾顾', '乾風' => '乾风', @@ -13692,6 +13687,8 @@ public static $zh2Hans = [ '山崎闇斋' => '山崎闇斋', '山崎闇齋' => '山崎闇斋', '岳託' => '岳讬', +'峯會' => '峰会', +'巔峯' => '巅峰', '巨著' => '巨著', '乾乾淨淨' => '干干净净', '乾乾脆脆' => '干干脆脆', @@ -13751,9 +13748,12 @@ public static $zh2Hans = [ '承乾' => '承乾', '拉鍊' => '拉链', '拙著' => '拙著', -'拚命' => '拚命', -'拚搏' => '拚搏', -'拚死' => '拚死', +'拚却' => '拚却', +'拚卻' => '拚却', +'拚弃' => '拚弃', +'拚棄' => '拚弃', +'拚生尽死' => '拚生尽死', +'拚生盡死' => '拚生尽死', '拾瀋' => '拾渖', '挨剋' => '挨剋', '提昇' => '提升', @@ -13858,6 +13858,8 @@ public static $zh2Hans = [ '氾濫' => '泛滥', '洗鍊' => '洗练', '瀋液' => '渖液', +'满拚自尽' => '满拚自尽', +'滿拚自盡' => '满拚自尽', '薰習' => '熏习', '薰心' => '熏心', '薰沐' => '熏沐', @@ -14007,6 +14009,7 @@ public static $zh2Hans = [ '讎正' => '雠正', '讎問' => '雠问', '雪鍊' => '雪链', +'頂峯' => '顶峰', '項鍊' => '项链', '飛昇' => '飞升', '飭令' => '飭令', @@ -14284,6 +14287,10 @@ public static $zh2TW = [ '希拉莉' => '希拉蕊', '希拉里' => '希拉蕊', '希特拉' => '希特勒', +'傷殘奧林匹克' => '帕拉林匹克', +'残疾人奥林匹克' => '帕拉林匹克', +'残奥会' => '帕運會', +'殘奧會' => '帕運會', '巴尔米拉环礁' => '帕邁拉環礁', '帕劳' => '帛琉', '希拉克' => '席哈克', @@ -14666,7 +14673,6 @@ public static $zh2TW = [ '劳拉' => '蘿拉', '荧光' => '螢光', '荧屏' => '螢屏', -'屏幕' => '螢幕', '行人路权' => '行人路權', '行人路權' => '行人路權', '流動作業系統' => '行動作業系統', @@ -14805,6 +14811,8 @@ public static $zh2TW = [ '馬里共和國' => '馬利共和國', '马里共和国' => '馬利共和國', '马拉维' => '馬拉威', +'馬勒當拿' => '馬拉度納', +'马拉多纳' => '馬拉度納', '馬斯特里赫特' => '馬斯垂克', '马斯特里赫特' => '馬斯垂克', '马耳他' => '馬爾他', @@ -14927,6 +14935,7 @@ public static $zh2HK = [ '伴著者' => '伴著者', '伴著述' => '伴著述', '伴著錄' => '伴著錄', +'服务器' => '伺服器', '布下了' => '佈下了', '布下的' => '佈下的', '布光' => '佈光', @@ -15174,6 +15183,7 @@ public static $zh2HK = [ '侵占' => '侵佔', '促著' => '促着', '俄占' => '俄佔', +'奧勒岡' => '俄勒岡', '保障著' => '保障着', '保障著作' => '保障著作', '保障著名' => '保障著名', @@ -15245,6 +15255,8 @@ public static $zh2HK = [ '備著者' => '備著者', '備著述' => '備著述', '備著錄' => '備著錄', +'帕拉林匹克' => '傷殘奧林匹克', +'残疾人奥林匹克' => '傷殘奧林匹克', '傻里傻气' => '傻裏傻氣', '雇员' => '僱員', '雇用' => '僱用', @@ -16359,6 +16371,7 @@ public static $zh2HK = [ '历史里' => '歷史裏', '死里求生' => '死裏求生', '死里逃生' => '死裏逃生', +'帕運會' => '殘奧會', '殺著' => '殺着', '殺著作' => '殺著作', '殺著名' => '殺著名', @@ -17157,7 +17170,6 @@ public static $zh2HK = [ '蘸著錄' => '蘸著錄', '蜜里调油' => '蜜裏調油', '荧屏' => '螢屏', -'屏幕' => '螢幕', '人行道' => '行人路', '行家里手' => '行家裏手', '首席执行官' => '行政總裁', @@ -17775,6 +17787,8 @@ public static $zh2HK = [ '糊口' => '餬口', '馬里蘭' => '馬利蘭', '马里兰' => '馬利蘭', +'馬拉度納' => '馬勒當拿', +'马拉多纳' => '馬勒當拿', '马拉特·萨芬' => '馬拉特·沙芬', '馬斯垂克' => '馬斯特里赫特', '馬爾地夫' => '馬爾代夫', @@ -18092,6 +18106,7 @@ public static $zh2CN = [ '可攜式' => '便携式', '攜帶型' => '便携式', '促著' => '促着', +'奧勒岡' => '俄勒冈', '保護著' => '保护着', '保鑣' => '保镖', '保障著' => '保障着', @@ -18654,7 +18669,6 @@ public static $zh2CN = [ '尼日爾' => '尼日尔', '區域網' => '局域网', '區域網路' => '局域网络', -'螢幕' => '屏幕', '展著' => '展着', '展著書' => '展著书', '展著作' => '展著作', @@ -19308,6 +19322,9 @@ public static $zh2CN = [ '梵谷' => '梵高', '欠帳' => '欠账', '死帳' => '死账', +'帕運會' => '残奥会', +'傷殘奧林匹克' => '残疾人奥林匹克', +'帕拉林匹克' => '残疾人奥林匹克', '庇里牛斯' => '比利牛斯', '披索' => '比索', '畢卡索' => '毕加索', @@ -20511,6 +20528,8 @@ public static $zh2CN = [ '營運長,' => '首席运营官,', '馬爾地夫' => '马尔代夫', '萌島' => '马恩岛', +'馬勒當拿' => '马拉多纳', +'馬拉度納' => '马拉多纳', '馬拉威' => '马拉维', '馬斯垂克' => '马斯特里赫特', '馬爾他' => '马耳他', diff --git a/languages/i18n/ce.json b/languages/i18n/ce.json index a74dd08b02..351e40ebfa 100644 --- a/languages/i18n/ce.json +++ b/languages/i18n/ce.json @@ -3012,6 +3012,7 @@ "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]» агӀонг коммент тӀетоха хьан декъашхочун цӀарца, кхин лелош йолу браузер билгал еш.", diff --git a/languages/i18n/cv.json b/languages/i18n/cv.json index f9b66cdd70..9af37c140a 100644 --- a/languages/i18n/cv.json +++ b/languages/i18n/cv.json @@ -115,7 +115,7 @@ "about": "Ăнлантарни", "article": "Статья", "newwindow": "(çĕнĕ чӳречере)", - "cancel": "Пăрахăçла", + "cancel": "Çырмасăр хăвар", "moredotdotdot": "Малалла…", "mypage": "Страница", "mytalk": "Сӳтсе явни", @@ -277,6 +277,7 @@ "yourname": "Усă куракан ят:", "userlogin-yourname": "Усă куракан ят", "yourpassword": "Вăрттăн сăмах:", + "userlogin-yourpassword": "Пароль", "yourpasswordagain": "Вăрттăн сăмах тепре çырăр:", "yourdomainname": "Сирĕн доменă:", "login": "Кĕрĕр", @@ -299,7 +300,7 @@ "nosuchuser": "$1 ятлă хутшăнакан çук.\nÇырнă ята тепĕр хут тĕрĕслĕр, е аяларах вырнаçнă формăна усă курса çĕнĕ хутшăнакана регистрацилĕр.", "nosuchusershort": "$1 ятлă хутшăнакан çук. Ятне епле çырнине тĕрĕслĕр.", "nouserspecified": "Сирĕн хутшăнаканăн ятне каламалла.", - "wrongpassword": "Эсир кăтартнă вăрттăн сăмах тĕрĕс мар. Урăххине кăтартăр.", + "wrongpassword": "Ятă е парольĕ тĕрĕс мар.\nТепĕр хут кĕртĕр.", "wrongpasswordempty": "Пушă мар пароль çырăр тархасшăн.", "mailmypassword": "Çĕнĕ вăрттăн сăмаха ярса ил", "passwordremindertitle": "{{grammar:genitive|{{SITENAME}}}} хутшăнаканăн вăрттăн сăмахне асаилтересси", @@ -370,6 +371,7 @@ "accmailtext": "$1 вăрттăн сăмахне кунта леçрĕмĕр: $2.", "newarticle": "(Çĕнни)", "newarticletext": "Ссылка урлă эсир халлĕхе çук статья çине куçрăр.\nÇĕнĕ статьяна кĕртес тесен аяларах вырнаçнă чӳречере текста çырăр.\n(тĕплĕнрех пĕлес тесен [$1 пулăшу страниципе] паллашăр).\nЕнчен те эсир кунта йăнăшпа лекнĕ пулсан — сирĕн браузерăн Каялла кнопка çине пусăр.", + "userpage-userdoesnotexist-view": "\"$1\" аккаунтне туман.", "usercsspreview": "'''Ан манăр, эсир сирĕн css файл епле пулассине çеç куратăр, ăна халлĕхе çырса хуман!'''", "userjspreview": "'''Астăвăр, ку сирĕн javascript-файлăн малтанхи курăмĕ кăна, ăна хальлĕхе çырса хуман!'''", "updated": "(Çĕнелнĕ)", @@ -392,6 +394,7 @@ "templatesusedsection": "Ку пайĕнче усă куракан {{PLURAL:$1|шаблон|шаблонсем}}:", "template-protected": "(сыхланă)", "template-semiprotected": "(пĕр пайне сыхланă)", + "content-model-wikitext": "викитекст", "expensive-parserfunction-category": "Кунта эсир чылай ресурс ыйтакан функцисемпе нумай ĕçлекен страницăсене куратăр", "post-expand-template-argument-category": "Шаблон аргуменчĕсене сиктерсе хăварнă страницăсем", "undo-norev": "Ку тӳрлетĕве пăрахăçлама май çук — вăл е пулман та, е ăна кăларса пăрахнă.", @@ -409,7 +412,7 @@ "last": "малт.", "page_first": "пĕрремĕш", "page_last": "юлашки", - "history-fieldset-title": "Историне пăх", + "history-fieldset-title": "Улăшăннисене шыра", "histfirst": "киввисем", "histlast": "çĕннисем", "historysize": "({{PLURAL:$1|1 байт|$1 байт}})", @@ -546,6 +549,8 @@ "recentchanges-submit": "Кăтарт", "rcfilters-legend-heading": "Кĕскетнисем:", "rcfilters-other-review-tools": "Урăх пăхмаллисем", + "rcfilters-activefilters-hide": "Кăтартмалла мар", + "rcfilters-activefilters-show": "Кăтартмалла", "rcfilters-show-new-changes": "Çĕнĕ улăшăннисем", "rcfilters-filterlist-title": "Фильтрсем", "rcfilters-filter-editsbyself-label": "Хăвăр улăштарнисем", @@ -719,9 +724,10 @@ "notargettitle": "Тĕллевне кăтартман", "pager-newer-n": "{{PLURAL:$1|çĕнĕреххисене 1|$1 çĕнĕреххисене}}", "pager-older-n": "{{PLURAL:$1|кивĕреххисене 1|$1 кивĕреххисене}}", + "apihelp-no-such-module": "\"$1\" модульĕ тупăнмарĕ.", "booksources": "Кĕнекесен çăлкуçĕсем", "booksources-search": "Туп", - "specialloguserlabel": "Хутшăнакан:", + "specialloguserlabel": "Тăвакан:", "log": "Логсем", "logeventslist-submit": "Кăтарт", "all-logs-page": "Пĕтĕм логсем", @@ -771,6 +777,7 @@ "unwatch": "ан сăна", "unwatchthispage": "Сăнама пăрах", "notanarticle": "Ку статья мар", + "watchlist-hide": "Кăтартмалла мар", "watchlist-submit": "Кăтарт", "watching": "Сăнамаллисем шутне хушасси…", "unwatching": "Сăнав ят-йышĕнчен кăларса пăрахасси…", @@ -821,6 +828,7 @@ "mycontris": "Хушни", "anoncontribs": "Хушни", "contribsub2": "{{GENDER:$3|$1}} валли ($2)", + "contributions-userdoesnotexist": "\"$1\" аккаунтне туман.", "uctop": "(хальхи)", "month": "Уйăхран (маларах та):", "year": "Çултан (маларах та):", @@ -888,7 +896,7 @@ "allmessages-filter-submit": "Куç", "allmessages-filter-translate": "Куçар", "thumbnail-more": "Пысăклатмалли", - "filemissing": "Файл тупăнмарĕ", + "filemissing": "Файлĕ тупăнмарĕ", "thumbnail_error": "Пĕчĕк ӳкерчĕке тăваймарăмăр: $1", "thumbnail_invalid_params": "Пĕчĕк ӳкерчĕкĕн параметрĕ йăнăш", "import": "Страницăсене импортласси", @@ -900,7 +908,7 @@ "import-noarticle": "Импортламалли страница çук!", "importlogpage": "Импорт журналĕ", "tooltip-pt-userpage": "Сирĕн хутшăнакан страници", - "tooltip-pt-mytalk": "Сирĕн канашлу страници", + "tooltip-pt-mytalk": "{{GENDER:|Сирĕн}} сӳтсе явакан страницу", "tooltip-pt-anontalk": "IP адресне сӳтсе явни", "tooltip-pt-preferences": "Сирĕн ĕнерлевсем", "tooltip-pt-watchlist": "Эсир пăхакан страницисем", @@ -1017,6 +1025,7 @@ "fileduplicatesearch": "Пĕр пек файлсен шыравĕ", "fileduplicatesearch-filename": "Файл ячĕ:", "fileduplicatesearch-submit": "Туп", + "fileduplicatesearch-noresults": "\"$1\" ятлă файл тупăнмарĕ.", "specialpages": "Ятарлă страницăсем", "specialpages-group-maintenance": "Техника обслуживанийĕн отчечĕсем", "specialpages-group-other": "Ытти ятарлă страницăсем", @@ -1028,6 +1037,7 @@ "tag-filter": "[[Special:Tags|Тегсен]] фильтрĕ:", "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Тег|Тегсем}}]]: $2)", "tags-title": "Тегсем", + "tags-tag": "Тегĕн ячĕ", "compare-submit": "Танлаштар", "htmlform-selectorother-other": "Урăххи", "htmlform-no": "Çук", @@ -1042,5 +1052,6 @@ "pagelang-select-lang": "Чĕлхе суйлăр", "mediastatistics-header-audio": "Аудио", "mediastatistics-header-video": "Видеосем", - "special-characters-group-symbols": "Символсем" + "special-characters-group-symbols": "Символсем", + "authmanager-userdoesnotexist": "\"$1\" аккаунтне туман." } diff --git a/languages/i18n/da.json b/languages/i18n/da.json index 43af650d9a..eebf96c59a 100644 --- a/languages/i18n/da.json +++ b/languages/i18n/da.json @@ -1258,7 +1258,7 @@ "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", diff --git a/languages/i18n/de-formal.json b/languages/i18n/de-formal.json index 9c8a5d5d6c..9773a6a9c3 100644 --- a/languages/i18n/de-formal.json +++ b/languages/i18n/de-formal.json @@ -25,7 +25,8 @@ "Florian", "Tiin", "Rhirsch", - "Metalhead64" + "Metalhead64", + "Vogone" ] }, "tog-hideminor": "Kleine Änderungen in den „Letzten Änderungen“ ausblenden", @@ -39,6 +40,7 @@ "tog-editsectiononrightclick": "Einzelne Abschnitte per Rechtsklick bearbeiten", "tog-enotifrevealaddr": "Ihre E-Mail-Adresse in Benachrichtigungs-E-Mails anzeigen", "cancel": "Abbrechen", + "search-ignored-headings": " #
\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 #
", "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.", @@ -48,18 +50,21 @@ "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 Ihrer Bearbeitung 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.", diff --git a/languages/i18n/el.json b/languages/i18n/el.json index b0ea8784a1..20bb78a699 100644 --- a/languages/i18n/el.json +++ b/languages/i18n/el.json @@ -3566,6 +3566,9 @@ "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", diff --git a/languages/i18n/fr.json b/languages/i18n/fr.json index cdc118c866..abfc0e4a91 100644 --- a/languages/i18n/fr.json +++ b/languages/i18n/fr.json @@ -796,8 +796,8 @@ "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": "Votre compte utilisateur ou votre adresse IP a été bloqué.\n\nLe blocage a été effectué par $1.\nLa raison invoquée est la suivante : $2.\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: $2.\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": "Votre compte utilisateur ou votre adresse IP a été bloqué.\n\nLe blocage a été effectué par $1.\nLa raison invoquée est la suivante : $2.\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: $2.\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: $2.\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.", diff --git a/languages/i18n/it.json b/languages/i18n/it.json index 5aad04712a..7a3e68d57a 100644 --- a/languages/i18n/it.json +++ b/languages/i18n/it.json @@ -1016,7 +1016,7 @@ "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", @@ -1856,9 +1856,9 @@ "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]]):", diff --git a/languages/i18n/jv.json b/languages/i18n/jv.json index 84496e8238..083fd5f458 100644 --- a/languages/i18n/jv.json +++ b/languages/i18n/jv.json @@ -660,7 +660,7 @@ "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": "Pélik: Panjenengan nggawé manèh kaca kang tau kabusak.\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é.", @@ -872,7 +872,7 @@ "showingresults": "Ing ngisor iki dituduhaké {{PLURAL:$1|'''1''' kasil|'''$1''' kasil}}, wiwitané saking #$2.", "showingresultsinrange": "Nuduhaké nganti {{PLURAL:$1|1 kasil|$1 kasil}} sajeroning penthangan #$2 tekan #$3.", "search-showingresults": "{{PLURAL:$4|Asil $1 saka $3|Asil $1 – $2 saka $3}}", - "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:", @@ -1336,7 +1336,7 @@ "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]] dikandeli.", @@ -1787,7 +1787,7 @@ "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", @@ -2515,16 +2515,16 @@ "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)", @@ -2573,7 +2573,7 @@ "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", diff --git a/languages/i18n/my.json b/languages/i18n/my.json index 6e76aaf757..f1a5324bef 100644 --- a/languages/i18n/my.json +++ b/languages/i18n/my.json @@ -2373,6 +2373,7 @@ "fileduplicatesearch-filename": "ဖိုင်အမည်:", "fileduplicatesearch-submit": "ရှာဖွေရန်", "specialpages": "အထူး စာမျက်နှာများ", + "specialpages-note-top": "မှတ်ချက်", "specialpages-note-restricted": "* ပုံမှန် အထူးစာမျက်နှာများ။\n* ကန့်သတ်ထားသော အထူးစာမျက်နှာများ။", "specialpages-group-maintenance": "ထိန်းသိမ်းမှု အစီရင်ခံချက်များ", "specialpages-group-other": "အခြားအထူးစာမျက်နှာများ", diff --git a/languages/i18n/nap.json b/languages/i18n/nap.json index 6b18e7184b..1918083adc 100644 --- a/languages/i18n/nap.json +++ b/languages/i18n/nap.json @@ -54,6 +54,7 @@ "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", @@ -363,6 +364,8 @@ "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.", @@ -1583,9 +1586,9 @@ "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.
\nL'elenco ccà abbascio fà vedé {{PLURAL:$1|'a primma paggena ca cullega|'e primme $1 paggene ca cullegano}} sulamente a stu file.
\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.
\nL'elenco ccà abbascio fà vedé {{PLURAL:$1|'a primma paggena ca buò|'e primme $1 paggene ca vonno}} sulamente a stu file.
\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]]):", @@ -2161,8 +2164,8 @@ "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", diff --git a/languages/i18n/pl.json b/languages/i18n/pl.json index 1e8ddfdd9f..3faa6bae05 100644 --- a/languages/i18n/pl.json +++ b/languages/i18n/pl.json @@ -2243,11 +2243,11 @@ "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", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 4b1717d900..b6558b690d 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -3874,11 +3874,11 @@ "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}}", diff --git a/languages/i18n/ro.json b/languages/i18n/ro.json index f9bfa779ba..c028e0ad51 100644 --- a/languages/i18n/ro.json +++ b/languages/i18n/ro.json @@ -382,6 +382,9 @@ "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.", diff --git a/languages/i18n/sah.json b/languages/i18n/sah.json index 0bad36edeb..b7218faeb6 100644 --- a/languages/i18n/sah.json +++ b/languages/i18n/sah.json @@ -2321,7 +2321,7 @@ "blanknamespace": "(Сүрүн)", "contributions": "{{GENDER:$1|Кыттааччы}} суруйуута (кылаата)", "contributions-title": "$1 кыттааччы киллэрбит уларытыылара", - "mycontris": "Суруйуу тиһигэ", + "mycontris": "Суруйуу испииһэгэ", "anoncontribs": "Суруйуу тиһилигэ", "contribsub2": "$1 ($2) суруйуута", "contributions-userdoesnotexist": "Маннык \"$1\" кыттааччы аата бэлиэтэниллибэтэх.", diff --git a/languages/i18n/shi.json b/languages/i18n/shi.json index 1e285b5722..8ab9ee4a70 100644 --- a/languages/i18n/shi.json +++ b/languages/i18n/shi.json @@ -54,17 +54,17 @@ "sunday": "ⴰⵙⴰⵎⴰⵙ", "monday": "ⴰⵢⵏⴰⵙ", "tuesday": "ⴰⵙⵉⵏⴰⵙ", - "wednesday": "Akras", - "thursday": "ⴰⴽⵡⴰⵙ", - "friday": "ⴰⵙⵉⵎⵡⴰⵙ", - "saturday": "asidyas", - "sun": "asamas", - "mon": "Aynas", - "tue": "Asinas", - "wed": "Akras", - "thu": "Akwas", - "fri": "Asimwas", - "sat": "Asidyas", + "wednesday": "ⵍⴰⵔⴱⵄ", + "thursday": "ⵍⵅⵎⵉⵙ", + "friday": "ⵍⵊⴰⵎⵄ", + "saturday": "ⵙⵙⴱⵜ", + "sun": "ⵍⵃⴷⴷ", + "mon": "ⵍⵜⵏⵉⵏ", + "tue": "ⵟⵟⵍⴰⵜⴰ", + "wed": "ⵍⴰⵔⴱⵄ", + "thu": "ⵍⵅⵎⵉⵙ", + "fri": "ⵍⵊⴰⵎⵄ", + "sat": "ⵙⵙⴱⵜ", "january": "ⵉⵏⵏⴰⵢⵔ", "february": "ⴼⴱⵔⴰⵢⵔ", "march": "ⵎⴰⵔⵚ", @@ -73,10 +73,10 @@ "june": "ⵢⵓⵏⵢⵓ", "july": "ⵢⵓⵍⵢⵓⵣ", "august": "ⵖⵓⵛⵜ", - "september": "ⵛⵓⵜⴰⵏⴱⵉⵔ", + "september": "ⵛⵓⵜⴰⵎⴱⵉⵔ", "october": "ⴽⵜⵓⴱⵔ", - "november": "ⵏⵓⵡⴰⵏⴱⵉⵔ", - "december": "ⴷⵓⵊⴰⵏⴱⵉⵔ", + "november": "ⵏⵓⵡⴰⵎⴱⵉⵔ", + "december": "ⴷⵓⵊⴰⵎⴱⵉⵔ", "january-gen": "ⵉⵏⵏⴰⵢⵔ", "february-gen": "ⴼⴱⵔⴰⵢⵔ", "march-gen": "ⵎⴰⵔⵚ", @@ -85,13 +85,13 @@ "june-gen": "ⵢⵓⵏⵢⵓ", "july-gen": "ⵢⵓⵍⵢⵓⵣ", "august-gen": "ⵖⵓⵛⵜ", - "september-gen": "ⵛⵓⵜⴰⵏⴱⵉⵔ", + "september-gen": "ⵛⵓⵜⴰⵎⴱⵉⵔ", "october-gen": "ⴽⵜⵓⴱⵔ", "november-gen": "ⵏⵓⵡⴰⵎⴱⵉⵔ", - "december-gen": "ⴷⵓⵊⴰⵏⴱⵉⵔ", + "december-gen": "ⴷⵓⵊⴰⵎⴱⵉⵔ", "jan": "ⵉⵏⵏ", "feb": "brayr", - "mar": "ⵎⴰⵔ", + "mar": "ⵎⴰⵕ", "apr": "ⴰⴱⵔ", "may": "ⵎⴰⵢ", "jun": "ⵢⵓⵏ", @@ -106,10 +106,10 @@ "june-date": "$1 ⵢⵓⵏⵢⵓ", "july-date": "$1 ⵢⵓⵍⵢⵓⵣ", "august-date": "$1 ⵖⵓⵛⵜ", - "september-date": "$1 ⵛⵓⵜⴰⵏⴱⵉⵔ", + "september-date": "$1 ⵛⵓⵜⴰⵎⴱⵉⵔ", "october-date": "$1 ⴽⵜⵓⴱⵔ", - "november-date": "$1 ⵏⵓⵡⴰⵏⴱⵉⵔ", - "december-date": "$1 ⴷⵓⵊⴰⵏⴱⵉⵔ", + "november-date": "$1 ⵏⵓⵡⴰⵎⴱⵉⵔ", + "december-date": "$1 ⴷⵓⵊⴰⵎⴱⵉⵔ", "pagecategories": "{{PLURAL:$1|ⵜⴰⴳⴳⴰⵢⵜ|ⵜⴰⴳⴳⴰⵢⵉⵏ}}", "category_header": "ⵜⴰⵙⵏⵉⵡⵉⵏ ⵏ ⵜⴰⴳⴳⴰⵢⵜ \"$1\"", "subcategories": "ⵜⵉⴷⵓⴳⴳⴰⵢⵉⵏ", @@ -155,7 +155,7 @@ "updatedmarker": "Tuybddal z tizrink li iğuran", "printableversion": "ⴰⵎⴱⵔⵉⵎⵉ ⵜⴰⵙⵏⴰ ⴰⴷ", "permalink": "Azday Bdda illan", - "print": "Siggz", + "print": "ⴰⵎⴱⵔⵉⵎⵉ", "edit": "ⵙⵏⴼⵍ", "create": "ⵙⵏⵓⵍⴼⵓ", "delete": "ⴽⴽⵙ", diff --git a/languages/i18n/sq.json b/languages/i18n/sq.json index d1f3d62cfe..72e7ea5d0f 100644 --- a/languages/i18n/sq.json +++ b/languages/i18n/sq.json @@ -319,7 +319,7 @@ "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": "Ju keni kërkuar një faqe speciale të pavlefshme. \n\n Një listë e faqeve speciale të vlefshme mund të gjendet në [[Special:SpecialPages|{{int: specialpages }}]].", + "nospecialpagetext": "Ju keni kërkuar një faqe speciale të pavlefshme. \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.", diff --git a/languages/i18n/sr-ec.json b/languages/i18n/sr-ec.json index 24b86b89ae..6f69618465 100644 --- a/languages/i18n/sr-ec.json +++ b/languages/i18n/sr-ec.json @@ -42,21 +42,21 @@ "Stalker" ] }, - "tog-underline": "Подвлачење веза:", + "tog-underline": "Подвлачење линкова:", "tog-hideminor": "Сакриј мање измене са списка скорашњих измена", "tog-hidepatrolled": "Сакриј патролиране измене са списка скорашњих измена", "tog-newpageshidepatrolled": "Сакриј патролиране странице са списка нових страница", "tog-hidecategorization": "Сакриј категоризацију страница", "tog-extendwatchlist": "Прошири списак надгледања за поглед свих промена, не само скорашњих", "tog-usenewrc": "Групиши измене по страници у скорашњим изменама и списку надгледања", - "tog-numberheadings": "Аутоматски нумериши поднаслове", - "tog-showtoolbar": "Прикажи траку с алаткама за уређивање", + "tog-numberheadings": "Аутоматски нумериши наслове", + "tog-showtoolbar": "Прикажи траку са алаткама за уређивање", "tog-editondblclick": "Уреди странице двоструким кликом", "tog-editsectiononrightclick": "Омогући уређивање одељака десним кликом на њихове наслове", "tog-watchcreations": "Додај странице које направим и датотеке које отпремим на мој списак надгледања", "tog-watchdefault": "Додај странице и датотеке које уредим на мој списак надгледања", "tog-watchmoves": "Додај странице и датотеке које преместим на мој списак надгледања", - "tog-watchdeletion": "Додај странице и датотеке које обришем на мој списак надгледања", + "tog-watchdeletion": "Додај странице и датотеке које избришем на мој списак надгледања", "tog-watchuploads": "Додај датотеке које отпремим на мој списак надгледања", "tog-watchrollback": "Додај странице на којима сам извршио враћање измена на мој списак надгледања", "tog-minordefault": "Означавај све измене као мање", @@ -65,10 +65,10 @@ "tog-enotifwatchlistpages": "Пошаљи ми имејл када се промени страница или датотека са мог списка надгледања", "tog-enotifusertalkpages": "Пошаљи ми имејл кад се промени моја корисничка страница за разговор", "tog-enotifminoredits": "Пошаљи ми имејл и код мањих измена страница и датотека", - "tog-enotifrevealaddr": "Откриј моју имејл адресу у порукама обавештења", + "tog-enotifrevealaddr": "Откриј моју имејл-адресу у порукама обавештења", "tog-shownumberswatching": "Прикажи број корисника који надгледају", "tog-oldsig": "Ваш постојећи потпис:", - "tog-fancysig": "Сматрај потпис као викитекст (без самоповезивања)", + "tog-fancysig": "Сматрај потпис као викитекст (без аутоматског линка)", "tog-uselivepreview": "Прикажи претпреглед без поновног учитавања странице", "tog-forceeditsummary": "Упозори ме када не унесем резиме измене", "tog-watchlisthideown": "Сакриј моје измене са списка надгледања", @@ -84,8 +84,8 @@ "tog-diffonly": "Не приказуј садржај странице испод разлика", "tog-showhiddencats": "Прикажи скривене категорије", "tog-norollbackdiff": "Не приказуј разлику након извршеног враћања", - "tog-useeditwarning": "Упозори ме када напуштам страницу за уређивање с несачуваним променама", - "tog-prefershttps": "Увек користи сигурну везу док сам пријављен/а.", + "tog-useeditwarning": "Упозори ме када напуштам страницу за уређивање са несачуваним променама", + "tog-prefershttps": "Увек користи сигурну везу док сам пријављен/на.", "underline-always": "увек", "underline-never": "никад", "underline-default": "према теми или прегледачу", @@ -173,7 +173,7 @@ "listingcontinuesabbrev": "наст.", "index-category": "Пописане странице", "noindex-category": "Непописане странице", - "broken-file-category": "Странице с неисправним везама до датотека", + "broken-file-category": "Странице са неисправним линковима до датотека", "categoryviewer-pagedlinks": "$1 ($2)", "category-header-numerals": "$1–$2", "about": "О нама", @@ -197,7 +197,7 @@ "tagline": "Извор: {{SITENAME}}", "help": "Помоћ", "search": "Претрага", - "search-ignored-headings": " #
\n# Наслови који ће бити занемарени при претрази.\n# Промене су видљиве одмах након што се страница са насловом попише.\n# Можете изнудити поновно пописивање „нултом” изменом.\n# Синтакса је следећа:\n#  * Сваки ред који започиње знаком „#” је коментар.\n#  * Сваки не празни ред је тачан наслов који ће бити занемарен, с тим да се разликују мала и велика слова и све остало\nРеференце\nСпољашње везе\nТакође погледајте\n #
", + "search-ignored-headings": " #
\n# Наслови који ће бити занемарени при претрази.\n# Промене су видљиве одмах након што се страница са насловом попише.\n# Можете изнудити поновно пописивање „нултом” изменом.\n# Синтакса је следећа:\n#  * Сваки ред који започиње знаком „#” је коментар.\n#  * Сваки не празни ред је тачан наслов који ће бити занемарен, с тим да се разликују мала и велика слова и све остало\nРеференце\nСпољашњи линкови\nТакође погледајте\n #
", "searchbutton": "Претражи", "go": "Иди", "searcharticle": "Иди", @@ -206,7 +206,7 @@ "history_small": "историја", "updatedmarker": "ажурирано од моје последње посете", "printableversion": "За штампање", - "permalink": "Трајна веза", + "permalink": "Трајни линк", "print": "Штампај", "view": "Погледај", "view-foreign": "Погледај на пројекту $1", @@ -214,9 +214,9 @@ "edit-local": "Уреди локални опис", "create": "Направи", "create-local": "Додај локални опис", - "delete": "Обриши", - "undelete_short": "Врати {{PLURAL:$1|обрисану измену|$1 обрисане измене|$1 обрисаних измена}}", - "viewdeleted_short": "Погледај {{PLURAL:$1|једну обрисану измену|$1 обрисане измене|$1 обрисаних измена}}", + "delete": "Избриши", + "undelete_short": "Врати {{PLURAL:$1|избрисану измену|$1 избрисане измене|$1 избрисаних измена}}", + "viewdeleted_short": "Погледај {{PLURAL:$1|једну избрисану измену|$1 избрисане измене|$1 избрисаних измена}}", "protect": "Заштити", "protect_change": "промени", "unprotect": "Промени заштиту", @@ -302,7 +302,7 @@ "confirmable-no": "Не", "thisisdeleted": "Погледај или врати $1?", "viewdeleted": "Погледај $1?", - "restorelink": "{{PLURAL:$1|једну обрисану измену|$1 обрисане измене|$1 обрисаних измена}}", + "restorelink": "{{PLURAL:$1|једну избрисану измену|$1 избрисане измене|$1 избрисаних измена}}", "feedlinks": "Фид:", "feed-invalid": "Неважећи тип пријаве на фид.", "feed-unavailable": "Фидови синдикације нису доступни", @@ -327,7 +327,7 @@ "nstab-category": "Категорија", "mainpage-nstab": "Главна страна", "nosuchaction": "Нема такве радње", - "nosuchactiontext": "Радња наведена у URL-у није валидна.\nМожда сте откуцали погрешан URL-а или сте пратили покварену везу.\nОво такође може да указује на грешку у софтверу који користи {{SITENAME}}.", + "nosuchactiontext": "Радња која је наведена у URL-у није важећа.\nМожда сте откуцали погрешан URL-а или сте пратили покварен линк.\nОво такође може да указује на грешку у софтверу који користи {{SITENAME}}.", "nosuchspecialpage": "Нема такве посебне странице", "nospecialpagetext": "Захтевали сте невалидну посебну страницу.\n\nСписак валидних посебних страница може да се пронађе на „[[Special:SpecialPages|{{int:specialpages}}]]”.", "error": "Грешка", @@ -341,7 +341,7 @@ "readonly": "База података је закључана", "enterlockreason": "Унесите разлог за закључавање, укључујући и време откључавања", "readonlytext": "База података је тренутно закључана, што значи да је није могуће мењати.\n\nСистемски администратор је навео следеће објашњење: $1", - "missing-article": "Текст странице под називом „$1“ ($2) није пронађен.\n\nУзрок ове грешке је обично застарела измена или веза до обрисане странице.\n\nАко се не ради о томе, онда сте вероватно пронашли грешку у софтверу.\nПријавите је [[Special:ListUsers/sysop|администратору]] уз одговарајућу везу.", + "missing-article": "Текст странице под називом „$1“ ($2) није пронађен.\n\nУзрок ове грешке је обично застарела измена или линк до избрисане странице.\n\nАко се не ради о томе, онда сте вероватно пронашли грешку у софтверу.\nПријавите је [[Special:ListUsers/sysop|администратору]] уз одговарајући линк.", "missingarticle-rev": "(ревизија#: $1)", "missingarticle-diff": "(разлика: $1, $2)", "readonly_lag": "База података је аутоматски закључана да би се секундарни сервери базе података ускладили с главним.", @@ -350,7 +350,7 @@ "internalerror-fatal-exception": "Грешка необрађеног изузетка типа „$1“", "filecopyerror": "Не могу да копирам датотеку „$1“ у „$2“.", "filerenameerror": "Не могу да преименујем датотеку „$1“ у „$2“.", - "filedeleteerror": "Не могу да обришем датотеку „$1“.", + "filedeleteerror": "Не могу да избришем датотеку „$1“.", "directorycreateerror": "Не могу да направим директоријум „$1“.", "directoryreadonlyerror": "Директоријум „$1“ је само за читање.", "directorynotreadableerror": "Директоријум „$1“ није читљив.", @@ -358,21 +358,21 @@ "unexpected": "Неочекивана вредност: „$1“=„$2“.", "formerror": "Грешка: не могу да пошаљем образац.", "badarticleerror": "Ова радња се не може извршити на овој страници.", - "cannotdelete": "Не могу да обришем страницу или датотеку „$1“.\nВероватно ју је неко други обрисао.", - "cannotdelete-title": "Не могу да обришем страницу „$1“", + "cannotdelete": "Не могу да избришем страницу или датотеку „$1“.\nМогуће је да ју је неко већ избрисао.", + "cannotdelete-title": "Не могу да избришем страницу „$1“", "delete-hook-aborted": "Брисање је прекинула кука.\nНије дато никакво образложење.", "no-null-revision": "Не могу да направим нову ништавну ревизију странице „$1“", "badtitle": "Лош наслов", - "badtitletext": "Захтевани наслов странице је неважећи, празан или је погрешно повезан међујезички или међувики наслов.\nМожда садржи један или више знакова који се не могу користити у насловима.", + "badtitletext": "Тражени наслов странице је неважећи, празан или је погрешно повезан међујезички или међувики наслов.\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": "Тражени наслов странице садржи неважећи след магичног знака тилда (~~~).", "title-invalid-too-long": "Тражени назив странице је предугачак. Не сме бити дужи од $1 {{PLURAL:$1|бајта|бајтова}} у UTF-8 кодирању.", - "title-invalid-leading-colon": "Захтевани наслов странице садржи неважећу двотачку на почетку.", + "title-invalid-leading-colon": "Тражени наслов странице садржи неважећу двотачку на почетку.", "perfcached": "Следећи подаци су кеширани и можда нису ажурирани. У кешу {{PLURAL:$1|је доступан највише један резултат|су доступна највише $1 резултата|је доступно највише $1 резултата}}.", "perfcachedts": "Следећи подаци су кеширани и последњи пут ажурирани на датум $2 у $3 ч. У кешу {{PLURAL:$4|је доступан највише један резултат|су доступна највише $4 резултата|је доступно највише $4 резултата}}.", "querypage-no-updates": "Ажурирање ове странице је тренутно онемогућено.\nПодаци који се овде налазе могу бити застарели.", @@ -399,12 +399,12 @@ "titleprotected": "Овај назив је [[User:$1|$1]] заштитио од прављења. Разлог: $2.", "filereadonlyerror": "Не могу да изменим датотеку „$1“ јер је ризница „$2“ у режиму за читање.\n\nСистемски администратор је навео следеће објашњење: „$3“.", "invalidtitle": "Неважећи наслов", - "invalidtitle-knownnamespace": "Неисправан наслов с именским простором „$2“ и текстом „$3“", - "invalidtitle-unknownnamespace": "Неисправан наслов с именским простором бр. $1 и текстом „$2“", + "invalidtitle-knownnamespace": "Неважећи наслов са именским простором „$2“ и текстом „$3“", + "invalidtitle-unknownnamespace": "Неважећи наслов са непознатим именским простором бр. $1 и текстом „$2“", "exception-nologin": "Нисте пријављени", "exception-nologin-text": "Пријавите се да бисте приступили овој страници или радњи.", "exception-nologin-text-manual": "Морате бити $1 да бисте приступили овој страници или радњи.", - "virus-badscanner": "Неисправно подешавање: непознати скенер за вирусе: $1", + "virus-badscanner": "Лоша конфигурација: непознати скенер за вирусе: $1", "virus-scanfailed": "скенирање није успело (код $1)", "virus-unknownscanner": "непознати антивирус:", "logouttext": "Сада сте одјављени.\n\nЗапамтите да неке странице могу наставити да се приказују као да сте још увек пријављени, све док не очистите кеш свог прегледача.", @@ -448,11 +448,11 @@ "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": "Разлог", @@ -477,14 +477,14 @@ "nocookiesfornew": "Кориснички налог није отворен јер његов извор није потврђен.\nОмогућите колачиће на прегледачу и поново учитајте страницу.", "nocookiesforlogin": "{{int:nocookieslogin}}", "createacct-loginerror": "Налог је успешно направљен, али се не можете аутоматски пријавити. Пређите на [[Special:UserLogin|ручно пријављивање]].", - "noname": "Унели сте неисправно корисничко име.", + "noname": "Нисте навели важеће корисничко име.", "loginsuccesstitle": "Успешно пријављивање", "loginsuccess": "Пријављени сте на {{SITENAME}} као „$1”.", "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 знакова}}.", @@ -494,30 +494,30 @@ "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": "Морате да наведете валидну имејл адресу.", - "passwordsent": "Нова лозинка је послата на имејл адресу {{GENDER:$1|корисника|кориснице|корисника}} $1.\nПријавите се пошто је примите.", + "noemail": "Не постоји имејл-адреса за {{GENDER:$1|корисника|корисницу}} $1.", + "noemailcreate": "Морате да наведете важећу имејл-адресу.", + "passwordsent": "Нова лозинка је послата на имејл-адресу {{GENDER:$1|корисника|кориснице}} $1.\nПоново се пријавите након што је примите.", "blocked-mailpassword": "Уређивање са ваше IP адресе је блокирано. Ради спречавања злоупотребе, забрањена је и функција враћања лозинке са ње.", - "eauthentsent": "На наведену имејл адресу је послат потврдни код.\nПре него што пошаљемо даљње поруке, пратите упутства с имејла да бисте потврдили да сте Ви отворили налог.", + "eauthentsent": "Имејл о потврди је послат на наведену имејл-адресу.\nПре било којих других слања имејлова на налог, мораћете пратити упутства у имејлу да бисте потврдили да је налог заиста ваш.", "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": "На овом викију не можете променити имејл адресу налога.", + "emailauthenticated": "Ваша имејл-адреса је потврђена на дан $2 у $3 ч.", + "emailnotauthenticated": "Ваша имејл-адреса још није потврђена.\nНиједан имејл неће да буде послат ни у једном од следећих случајева.", + "noemailprefs": "Наведите имејл-адресу у својим подешавањима за оспособљавање ових могућности.", + "emailconfirmlink": "Потврдите своју имејл-адресу", + "invalidemailaddress": "Имејл-адреса не може да буде прихваћена јер је у неважећем облику.\nУнесите исправну адресу или оставите празно поље.", + "cannotchangeemail": "Имејл-адресе налога не могу да се промене на овом викију.", "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": "Пријави ме", @@ -525,7 +525,7 @@ "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": "Да бисте завршили пријаву, подесите нову лозинку овде.", @@ -548,19 +548,19 @@ "botpasswords-label-create": "Направи", "botpasswords-label-update": "Ажурирај", "botpasswords-label-cancel": "Откажи", - "botpasswords-label-delete": "Обриши", + "botpasswords-label-delete": "Избриши", "botpasswords-label-resetpassword": "Ресетуј лозинку", "botpasswords-label-grants": "Применљиве дозволе:", "botpasswords-label-grants-column": "Одобрено", "botpasswords-bad-appid": "Име бота „$1” није валидно.", "botpasswords-insert-failed": "Неуспело додавање бота под именом „$1”. Можда је већ додат?", - "botpasswords-update-failed": "Неуспело ажурирање бота под називом „$1”. Да ли је обрисан?", + "botpasswords-update-failed": "Неуспело ажурирање бота под називом „$1”. Да није избрисан?", "botpasswords-created-title": "Направљена лозинка бота", "botpasswords-created-body": "Лозинка за бота „$1” корисника „$2” је направљена.", "botpasswords-updated-title": "Лозинка бота промењена", "botpasswords-updated-body": "Лозинка за бота „$1” корисника „$2” је ажурирана.", - "botpasswords-deleted-title": "Обрисана лозинка бота", - "botpasswords-deleted-body": "Лозинка за бота „$1” корисника „$2” је обрисана.", + "botpasswords-deleted-title": "Избрисана лозинка бота", + "botpasswords-deleted-body": "Лозинка за бота „$1” {{GENDER:$2|корисника|кориснице}} „$2” је избрисана.", "botpasswords-no-provider": "BotPasswordsSessionProvider није доступан.", "botpasswords-restriction-failed": "Не можете се пријавити због ограничења лозинки за ботове.", "botpasswords-not-exist": "Корисник „$1“ нема лозинку бота „$2“.", @@ -584,30 +584,30 @@ "passwordreset-emaildisabled": "Имејл је онемогућен на овом викију.", "passwordreset-username": "Корисничко име:", "passwordreset-domain": "Домен:", - "passwordreset-email": "Имејл адреса:", + "passwordreset-email": "Имејл-адреса:", "passwordreset-emailtitle": "Детаљи налога на викију {{SITENAME}}", - "passwordreset-emailtext-ip": "Неко (вероватно Ви, с IP адресе $1) затражио је ресетовање ваше \nлозинке за пројекат {{SITENAME}} ($4). Следећи кориснички {{PLURAL:$3|налог је повезан|налози су повезани}} \nс овом имејл адресом:\n\n$2\n\n{{PLURAL:$3|Ова привремена лозинка|Ове привремене лозинке}} ће истећи за {{PLURAL:$5|један дан|$5 дана}}.\nТребате да се пријавите и одаберите нову лозинку одмах. Ако је неко други направио овај \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": "Неко (вероватно ви, са IP адресе $1) затражио је ресетовање ваше \nлозинке за пројекат {{SITENAME}} ($4). Следећи кориснички {{PLURAL:$3|налог је повезан|налози су повезани}} \nса овом имејл адресом:\n\n$2\n\n{{PLURAL:$3|Ова привремена лозинка|Ове привремене лозинке}} ће истећи за {{PLURAL:$5|један дан|$5 дана}}.\nОдмах требате да се пријавите и одаберите нову лозинку. Ако је неко други направио овај \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": "Ако је ово имејл адреса повезана са вашим налогом, подсетник о лозинци ће бити послат на имејл.", - "passwordreset-emailsentusername": "Ако сте навели имејл адресу приликом регистрације, биће послат имејл за ресетовање лозинке.", + "passwordreset-emailsentemail": "Ако је ова имејл-адреса повезана са вашим налогом, онда ће имејл о ресетовању лозинке бити послат.", + "passwordreset-emailsentusername": "Ако постоји имејл-адреса повезана са овим корисничким именом, онда ће имејл о ресетовању лозинке бити послат.", "passwordreset-nocaller": "Позивалац се мора навести", "passwordreset-nosuchcaller": "Позивалац не постоји: $1", "passwordreset-ignored": "Ресетовање лозинке није успело. Можда послужилац није конфигурисан?", - "passwordreset-invalidemail": "Неисправна имејл адреса", + "passwordreset-invalidemail": "Неважећа имејл-адреса", "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)", @@ -618,10 +618,10 @@ "bold_tip": "Подебљан текст", "italic_sample": "Искошен текст", "italic_tip": "Искошен текст", - "link_sample": "Наслов везе", - "link_tip": "Унутрашња веза", - "extlink_sample": "http://www.example.com/ наслов везе", - "extlink_tip": "Спољашња веза (с префиксом http://)", + "link_sample": "Наслов линка", + "link_tip": "Унутрашњи линк", + "extlink_sample": "http://www.example.com/ наслов линка", + "extlink_tip": "Спољашњи линк (са префиксом http://)", "headline_sample": "Текст наслова", "headline_tip": "Поднаслов (ниво 2)", "nowiki_sample": "Овде уметните необликован текст", @@ -629,7 +629,7 @@ "image_sample": "Пример.jpg", "image_tip": "Уграђивање датотеке", "media_sample": "Пример.ogg", - "media_tip": "Веза", + "media_tip": "Линк до датотеке", "sig_tip": "Ваш потпис са временском ознаком", "hr_tip": "Водоравна линија (користите ретко)", "summary": "Резиме:", @@ -637,13 +637,13 @@ "minoredit": "Ово је мања измена", "watchthis": "Надгледај ову страницу", "savearticle": "Сачувај страницу", - "savechanges": "Сачувај измене", + "savechanges": "Сачувај промене", "publishpage": "Објави страницу", - "publishchanges": "Објави измене", + "publishchanges": "Објави промене", "savearticle-start": "Сачувај страницу...", - "savechanges-start": "Сачувај измене...", + "savechanges-start": "Сачувај промене...", "publishpage-start": "Објави страницу...", - "publishchanges-start": "Објави измене...", + "publishchanges-start": "Објави промене...", "preview": "Претпреглед", "showpreview": "Прикажи претпреглед", "showdiff": "Прикажи промене", @@ -658,13 +658,13 @@ "subject-preview": "Преглед теме:", "previewerrortext": "Дошло је до грешке при покушају прегледа промена.", "blockedtitle": "Корисник је блокиран", - "blockedtext": "Ваше корисничко име или IP адреса је блокирана.\n\nБлокирање је {{GENDER:$4|извршио|извршила}} $1.\nРазлог је $2.\n\n* Почетак блокаде: $8\n* Крај блокаде: $6\n* Блокирани корисник: $7\n\nМожете да контактирате {{GENDER:$4|корисника|корисницу}} $1 или другог [[{{MediaWiki:Grouppage-sysop}}|администратора]] да бисте разговарали о блокади.\nНе можете да користите могућност „{{int:emailuser}}” осим ако сте навели ваљану имејл адресу у својим [[Special:Preferences|подешавањима налога]] и нисте блокирани од коришћења исте.\nВаша актуелна IP адреса је $3, а ID блокаде #$5.\nУкључите све горње детаље при прављењу било каквих упита.", + "blockedtext": "Ваше корисничко име или IP адреса је блокирана.\n\nБлокирање је {{GENDER:$4|извршио|извршила}} $1.\nРазлог је $2.\n\n* Почетак блокирања: $8\n* Истек блокирања: $6\n* Блокирани: $7\n\nМожете да контактирате {{GENDER:$4|корисника|корисницу}} $1 или другог [[{{MediaWiki:Grouppage-sysop}}|администратора]] да бисте разговарали о блокирању.\nНе можете да користите могућност „{{int:emailuser}}” осим ако сте навели валидну имејл адресу у својим [[Special:Preferences|подешавањима налога]] и нисте блокирани од коришћења исте.\nВаша актуелна IP адреса је $3, а ID блокаде #$5.\nНаведите све горње детаље при прављењу било каквих упита.", "autoblockedtext": "Ваша IP адреса је аутоматски блокирана јер ју је користио други корисник, кога је {{GENDER:$4|блокирао|блокирала}} $1.\nРазлог:\n\n:$2\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": "Покушали сте да уредите одељак који не постоји.\nМожда је премештен или обрисан док сте прегледали страницу.", + "nosuchsectiontext": "Покушали сте да уредите одељак који не постоји.\nМожда је премештен или избрисан док сте прегледали страницу.", "loginreqtitle": "Потребна је пријава", "loginreqlink": "пријављени", "loginreqpagetext": "Морате бити $1 да бисте видели друге странице.", @@ -675,7 +675,7 @@ "anontalkpagetext": "----\nОво је страница за разговор с анонимним корисником који још нема налог или га не користи.\nЗбог тога морамо да користимо бројчану IP адресу како бисмо га препознали.\nТакву адресу може делити више корисника.\nАко сте анонимни корисник и мислите да су вам упућене примедбе, [[Special:CreateAccount|отворите налог]] или се [[Special:UserLogin|пријавите]] да бисте избегли будућу забуну с осталим анонимним корисницима.", "noarticletext": "На овој страници тренутно нема текста.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама,\n[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне извештаје] или [{{fullurl:{{FULLPAGENAME}}|action=edit}} направити ову страницу].", "noarticletext-nopermission": "Тренутно нема текста на овој страници.\nМожете да [[Special:Search/{{PAGENAME}}|потражите овај наслов странице]] на другим страницама или [{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражите сродне евиденције], али немате дозволу да направите ову страницу.", - "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": "Кориснички налог „$1“ није отворен.\nРазмислите да ли заиста желите да направите/уредите ову страницу.", "userpage-userdoesnotexist-view": "Кориснички налог „$1“ није отворен.", "blocked-notice-logextract": "Овај корисник је тренутно блокиран.\nПоследњи унос у евиденцији блокирања је наведен испод као референца:", @@ -694,7 +694,7 @@ "previewconflict": "Овај преглед осликава како ће изгледати текст у текстуалном оквиру.", "session_fail_preview": "Извињавамо се! Нисмо могли да обрадимо вашу измену због губитка података сесије.\n\nМожда сте одјављени. Проверите да ли сте пријављени и покушајте поново.\nАко и даље не ради, покушајте да се [[Special:UserLogout|одјавите]] и поново пријавите, те проверите да ли су на вашем претраживачу дозвољени колачићи са овог сајта.", "session_fail_preview_html": "Нисмо могли да обрадимо вашу измену због губитка података сесије.\n\nБудући да је на овом викију омогућен унос HTML ознака, преглед је сакривен као мера предострожности против напада преко јаваскрипта.\n\nАко сте покушали да направите праву измену, покушајте поново.\nАко и даље не ради, покушајте да се [[Special:UserLogout|одјавите]] и поново пријавите и проверите да ли ваш прегледач дозвољава колачиће са овог сајта.", - "token_suffix_mismatch": "Ваша измена је одбачена јер је ваш прегледач убацио знакове интерпункције у новчић уређивања.\nТо се понекад догађа када се користи неисправан посредник.", + "token_suffix_mismatch": "Ваша измена је одбијена јер је ваш клијент убацио знакове интерпункције у токен уређивања.\nИзмена је одбијена ради спречавања уништавања текста странице.\nОво се понекад догађа када користите проблематични анонимни посредник који је заснован на вебу.", "edit_form_incomplete": "Неки делови обрасца за уређивање нису стигли до сервера. Проверите да ли су ваше измене непромењене и покушајте поново.", "editing": "Уређујете $1", "creating": "Прављење странице $1", @@ -707,14 +707,14 @@ "editingold": "Упозорење: уређујете застарелу ревизију ове странице.\nАко је сачувате, све промене направљене од ове ревизије ће бити изгубљене.", "unicode-support-fail": "Ваш прегледач не подржава Unicode. Он је неопоходан за уређивање страница, па зато не могу сачувати измену.", "yourdiff": "Разлике", - "copyrightwarning": "Имајте на уму да се сви доприноси на овом викију сматрају као објављени под лиценцом $2 (више на $1).\nАко не желите да се ваши текстови мењају и размењују без ограничења, онда их не шаљите овде.
\nИсто тако обећавате да сте Ви аутор текста, или да сте га умножили с извора који је у јавном власништву.\nНе шаљите радове заштићене ауторским правима без дозволе!", + "copyrightwarning": "Имајте на уму да се сви доприноси на овом викију сматрају као објављени под лиценцом $2 (више на $1).\nАко не желите да се ваши текстови мењају и размењују без ограничења, онда их не шаљите овде.
\nИсто тако обећавате да сте Ви аутор текста, или да сте га умножили са извора који је у јавном власништву.\nНе шаљите радове заштићене ауторским правима без дозволе!", "copyrightwarning2": "Имајте на уму да се сви доприноси на овом викију могу мењати, враћати или брисати од других корисника.\nАко не желите да се ваши текстови слободно мењају и расподељују, не шаљите их овде.
\nИсто тако обећавате да сте ви аутор текста, или да сте га умножили с извора који је у јавном власништву (више на $1).\nНе шаљите радове заштићене ауторским правима без дозволе!", "editpage-cannot-use-custom-model": "Модел садржаја ове странице се не може променити.", "longpageerror": "Грешка: текст који сте унели је величине {{PLURAL:$1|један килобајт|$1 килобајта}}, што је веће од {{PLURAL:$2|дозвољеног једног килобајта|дозвољена $2 килобајта|дозвољених $2 килобајта}}.\nСтраница не може бити сачувана.", "readonlywarning": "Упозорење: база података је закључана ради одржавања, тако да тренутно нећете моћи да сачувате измене.\nМожда бисте желели сачувати текст за касније у некој текстуалној датотеци.\n\nСистемски администратор је навео следеће објашњење: $1", "protectedpagewarning": "Упозорење: Ова страница је заштићена, тако да само корисници са администраторским овлашћењима могу да је уређују.\nНајновији унос у евиденцији је наведен испод као референца:", "semiprotectedpagewarning": "Напомена: Ова страница је заштићена, тако да само аутоматски потврђени корисници могу да је уређују.\nНајновији унос у евиденцији је наведен испод као референца:", - "cascadeprotectedwarning": "Упозорење: Ова страница је заштићена, тако да само корисници са [[Special:ListGroupRights|одређеним правима]] могу да је уређују, јер је она укључена у {{PLURAL:$1|следећу страницу која је заштићена|следеће странице које су заштићене}} преносивом заштитом:", + "cascadeprotectedwarning": "Упозорење: Ова страница је заштићена тако да само корисници са [[Special:ListGroupRights|одређеним правима]] могу да је уређују, јер је укључена у {{PLURAL:$1|следећу страницу која је заштићена|следеће странице које су заштићене}} преносивом заштитом:", "titleprotectedwarning": "Упозорење: Ова страница је заштићена, тако да су потребна [[Special:ListGroupRights|посебна права]] да се она направи.\nНајновији унос у евиденцији је наведен испод као референца:", "templatesused": "{{PLURAL:$1|Шаблон који се користи|Шаблони који се користе}} на овој страници:", "templatesusedpreview": "{{PLURAL:$1|Шаблон|Шаблони}} у овом претпрегледу:", @@ -732,12 +732,12 @@ "permissionserrorstext": "Немате дозволу за ову радњу из {{PLURAL:$1|следећег|следећих}} разлога:", "permissionserrorstext-withaction": "Немате дозволу да $2 из {{PLURAL:$1|следећег|следећих}} разлога:", "contentmodelediterror": "Не можете уредити ову ревизију јер је њен модел садржаја $1, што се разликује од актуелног модела садржаја странице $2.", - "recreate-moveddeleted-warn": "Упозорење: поново правите страницу која је претходно обрисана.\n\nРазмотрите да ли је прикладно да наставите с уређивањем ове странице.\nОвде је наведена евиденција брисања и премештања с образложењем:", - "moveddeleted-notice": "Ова страница је обрисана.\nЕвиденција брисања, заштите и премештања странице је наведена испод као референца.", - "moveddeleted-notice-recent": "Жао нам је, ова страница је недавно обрисана (у последњих 24 сата).\nЕвиденција њеног брисања, заштите и премештања налази се испод:", + "recreate-moveddeleted-warn": "Упозорење: Поново правите страницу која је претходно избрисана.\n\nРазмотрите да ли је прикладно да наставите са уређивањем ове странице.\nОвде је наведена евиденција брисања и премештања са образложењем:", + "moveddeleted-notice": "Ова страница је избрисана.\nЕвиденција брисања, заштите и премештања странице је наведена испод као референца.", + "moveddeleted-notice-recent": "Нажалост, ова страница је недавно избрисана (у последњих 24 сата).\nЕвиденција брисања, заштите и премештања странице наведена је испод као референца:", "log-fulllog": "Погледај целу евиденцију", "edit-hook-aborted": "Измену је прекинула кука.\nНије дато никакво образложење.", - "edit-gone-missing": "Не могу да ажурирам страницу.\nИзгледа да је обрисана.", + "edit-gone-missing": "Не могу да ажурирам страницу.\nИзгледа да је избрисана.", "edit-conflict": "Сукоб измена.", "edit-no-change": "Ваша измена је занемарена јер није било никаквих промена у тексту.", "postedit-confirmation-created": "Страница је направљена.", @@ -747,7 +747,7 @@ "edit-already-exists": "Не могу да направим страницу.\nИзгледа да она већ постоји.", "defaultmessagetext": "Подразумевани текст поруке", "content-failed-to-parse": "Не могу да рашчланим садржај типа $2 за модел $1: $3", - "invalid-content-data": "Неисправни подаци садржаја", + "invalid-content-data": "Неважећи подаци садржаја", "content-not-allowed-here": "Садржај модела „$1“ није дозвољен на страници [[$2]]", "editwarning-warning": "Ако напустите ову страницу, изгубићете све измене које сте направили. Ако сте пријављени, можете онемогућити ово упозорење у својим подешавањима, у одељку „{{int:prefs-editing}}“.", "editpage-invalidcontentmodel-title": "Модел садржаја није подржан", @@ -787,7 +787,7 @@ "converter-manual-rule-error": "Пронађена је грешка у правилу за ручно претварање језика", "undo-success": "Измена се може поништити.\nПроверите разлике испод, па сачувајте измене.", "undo-failure": "Ова измена се не може поништити због сукоба измена.", - "undo-norev": "Не могу да вратим измену јер не постоји или је обрисана.", + "undo-norev": "Не могу да вратим измену јер не постоји или је избрисана.", "undo-nochange": "Изгледа да је измена већ поништена.", "undo-summary": "Поништена ревизија $1 {{GENDER:$2|корисника|кориснице}} [[Special:Contributions/$2|$2]] ([[User talk:$2|разговор]])", "undo-summary-username-hidden": "Поништи измену $1 скривеног корисника", @@ -807,9 +807,9 @@ "last": "разл", "page_first": "прва", "page_last": "последња", - "histlegend": "Избор разлика: означите кутијице ревизија за упоређивање и притисните enter или дугме на дну.
\nОбјашњење: ({{int:cur}}) = разлика с актуелном ревизијом, ({{int:last}}) = разлика с претходном ревизијом, {{int:minoreditletter}} = мања измена", + "histlegend": "Избор разлика: означите кутијице ревизија за упоређивање и притисните enter или дугме на дну.
\nОбјашњење: ({{int:cur}}) = разлика са актуелном ревизијом, ({{int:last}}) = разлика са претходном ревизијом, {{int:minoreditletter}} = мања измена", "history-fieldset-title": "Претрага измена", - "history-show-deleted": "Само обрисане ревизије", + "history-show-deleted": "Само избрисане ревизије", "histfirst": "најстарије", "histlast": "најновије", "historysize": "({{PLURAL:$1|1 бајт|$1 бајта|$1 бајтова}})", @@ -817,7 +817,7 @@ "history-feed-title": "Историја ревизија", "history-feed-description": "Историја измена ове странице на викију", "history-feed-item-nocomment": "$1 у $2", - "history-feed-empty": "Тражена страница не постоји.\nМогуће да је обрисана с викија или је преименована.\nПокушајте да [[Special:Search|претражите вики]] за сличне странице.", + "history-feed-empty": "Тражена страница не постоји.\nМогуће да је избрисана са викија или је преименована.\nПокушајте да [[Special:Search|претражите вики]] за релевантне нове странице.", "history-edit-tags": "Уреди ознаке изабраних ревизија", "rev-deleted-comment": "(опис измене уклоњен)", "rev-deleted-user": "(корисничко име уклоњено)", @@ -838,19 +838,19 @@ "rev-delundel": "промени видљивост", "rev-showdeleted": "прикажи", "revisiondelete": "Брисање/враћање ревизија", - "revdelete-nooldid-title": "Нема тражене измене", + "revdelete-nooldid-title": "Неважећа одредишна ревизија", "revdelete-nooldid-text": "Нисте навели одредишну ревизију на којој треба да се изврши ова функција, та ревизија не постоји, или покушавате да сакријете актуелну ревизију.", "revdelete-no-file": "Тражена датотека не постоји.", - "revdelete-show-file-confirm": "Јесте ли сигурни да желите да видите обрисану ревизију датотеке „$1“ од $2; $3?", + "revdelete-show-file-confirm": "Јесте ли сигурни да желите да видите избрисану ревизију датотеке „$1“ од $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": "Обрисани догађаји у евиденцијама ће се идаље појављивати у евиденцији, али ће делови њиховог садржаја бити недоступни јавности.", + "logdelete-text": "Избрисани догађаји у евиденцијама ће се идаље појављивати у евиденцији, али ће делови њиховог садржаја бити недоступни јавности.", "revdelete-text-others": "Остали администратори ће и даље моћи да приступе скривеном садржају и врате га, осим ако се поставе додатна ограничења.", - "revdelete-confirm": "Потврдите да намеравате ово урадити, да разумете последице и да то чините у складу с [[{{MediaWiki:Policy-url}}|правилима]].", + "revdelete-confirm": "Потврдите да намеравате ово урадити, да разумете последице и да то чините у складу са [[{{MediaWiki:Policy-url}}|правилима]].", "revdelete-suppress-text": "Сакривање измена би требало користити само у следећим случајевима:\n* злонамерни или погрдни подаци\n* неприкладни лични подаци\n*: кућна адреса и број телефона, број кредитне картице, ЈМБГ итд.", "revdelete-legend": "Ограничења видљивости", "revdelete-hide-text": "Текст ревизије", @@ -872,7 +872,7 @@ "logdelete-failure": "'''Не могу да поставим видљивост историје:'''\n$1", "revdel-restore": "промени видљивост", "pagehist": "Историја странице", - "deletedhist": "Обрисана историја", + "deletedhist": "Избрисана историја", "revdelete-hide-current": "Грешка при сакривању ставке од $1, $2: ово је актуелна ревизија.\nНе може да буде сакривена.", "revdelete-show-no-access": "Грешка при приказивању ставке од $1, $2: означена је као „ограничена“.\nНемате приступ до ње.", "revdelete-modify-no-access": "Грешка при мењању ставке од $1, $2: означена је као „ограничена“.\nНемате приступ до ње.", @@ -893,15 +893,15 @@ "mergehistory-from": "Изворна страница:", "mergehistory-into": "Одредишна страница:", "mergehistory-list": "Спојива историја измена", - "mergehistory-merge": "Следеће ревизије странице [[:$1]] могу се спојити са [[:$2]].\nКористите дугмиће у колони да бисте спојили ревизије које су направљене пре наведеног времена.\nКоришћење навигационих веза ће поништити ову колону.", + "mergehistory-merge": "Следеће ревизије странице [[:$1]] могу се спојити са [[:$2]].\nКористите дугмиће у колони да бисте спојили ревизије које су направљене пре наведеног времена.\nКоришћење навигационих линкова ће поништити ову колону.", "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": "Изворна и одредишна страница не могу бити исте.", @@ -932,7 +932,7 @@ "diff-multi-manyusers": "({{PLURAL:$1|Није приказана међуизмена|Нису приказане $1 међуизмене|Није приказано $1 међуизмена}} од више од $2 корисника)", "diff-paragraph-moved-tonew": "Пасус је премештен. Кликните да пређете на његово ново место.", "diff-paragraph-moved-toold": "Пасус је премештен. Кликните да пређете на његово старо место.", - "difference-missing-revision": "{{PLURAL:$2|Једна ревизија|$2 ревизије}} од ове разлике ($1) не {{PLURAL:$2|постоји|постоје}}.\n\nОво се обично дешава када пратите застарелу везу до странице која је обрисана.\nДетаље можете да пронађете у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} евиденцији брисања].", + "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“", @@ -1012,15 +1012,15 @@ "prefs-watchlist-managetokens": "Управљај жетонима", "prefs-misc": "Друга подешавања", "prefs-resetpass": "промени лозинку", - "prefs-changeemail": "промени или уклони имејл адресу", - "prefs-setemail": "постави имејл адресу", + "prefs-changeemail": "промени или уклони имејл-адресу", + "prefs-setemail": "постави имејл-адресу", "prefs-email": "Опције имејла", "prefs-rendering": "Изглед", "saveprefs": "Сачувај", "restoreprefs": "Врати сва подешавања на подразумеване вредности (у свим одељцима)", "prefs-editing": "Уређивање", "searchresultshead": "Претрага", - "stub-threshold": "Праг за обликовање везе као клице ($1):", + "stub-threshold": "Праг за обликовање линкова као клице ($1):", "stub-threshold-sample-link": "пример", "stub-threshold-disabled": "онемогућено", "recentchangesdays": "Број дана у скорашњим изменама:", @@ -1082,8 +1082,8 @@ "email": "Имејл", "prefs-help-realname": "Право име је опционално.\nАко је наведено, биће коришћено за приписивање вашег рада.", "prefs-help-email": "Имејл адреса је опционална, али је потребна за ресетовање лозинке, ако је заборавите.", - "prefs-help-email-others": "Такође можете изабрати да допустите другима да вас контактирају преко имејла путем везе на вашој корисничкој страници или страници за разговор.\nВаша имејл адреса неће бити приказана другим корисницима који вас контактирају.", - "prefs-help-email-required": "Потребна је имејл адреса.", + "prefs-help-email-others": "Такође можете изабрати да допустите другима да вас контактирају преко имејла путем линка на вашој корисничкој страници или страници за разговор.\nВаша имејл адреса неће бити приказана другим корисницима који вас контактирају.", + "prefs-help-email-required": "Имејл-адреса је неопходна.", "prefs-info": "Основне информације", "prefs-i18n": "Интернационализација", "prefs-signature": "Потпис", @@ -1129,7 +1129,7 @@ "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": "Сукоб промена корисничких права! Прегледајте и проверите ваше промене.", @@ -1164,7 +1164,7 @@ "right-autocreateaccount": "Пријавите се аутоматски са екстерним корисничким налогом", "right-minoredit": "означавање измена мањим", "right-move": "премештање страница", - "right-move-subpages": "премештање страница с њиховим подстраницама", + "right-move-subpages": "премештање страница са њиховим подстраницама", "right-move-rootuserpages": "премештање основних корисничких страница", "right-move-categorypages": "премештање категорија", "right-movefile": "премештање датотека", @@ -1181,13 +1181,13 @@ "right-apihighlimits": "коришћење виших граница за упите из API-ја", "right-writeapi": "коришћење API-ја за писање", "right-delete": "брисање страница", - "right-bigdelete": "брисање страница с великом историјом", + "right-bigdelete": "брисање страница са великом историјом", "right-deletelogentry": "брисање и враћање одређених уноса у евиденцији", "right-deleterevision": "брисање и враћање одређених ревизија страница", - "right-deletedhistory": "прегледање обрисаних ставки историје без повезаног текста", - "right-deletedtext": "прегледање обрисаног текста и промена између обрисаних ревизија", - "right-browsearchive": "претрага обрисаних страница", - "right-undelete": "враћање обрисаних страница", + "right-deletedhistory": "прегледање избрисаних ставки историје без повезаног текста", + "right-deletedtext": "прегледање избрисаног текста и промена између избрисаних ревизија", + "right-browsearchive": "претрага избрисаних страница", + "right-undelete": "враћање избрисаних страница", "right-suppressrevision": "прегледање, скривање и враћање одређених ревизија страница од свих корисника", "right-viewsuppressed": "прегледање измена скривених од свих корисника", "right-suppressionlog": "прегледање приватних евиденција", @@ -1209,8 +1209,8 @@ "right-editmyuserjs": "уређивање сопствених JavaScript датотека", "right-viewmywatchlist": "преглед сопственог списка надгледања", "right-editmywatchlist": "уређивање сопственог списка надгледања; неке предузете радње ће свеједно додати странице на списак и без овог права", - "right-viewmyprivateinfo": "преглед својих личних података (нпр. имејл адресу, право име)", - "right-editmyprivateinfo": "уређивање сопствених личних података (нпр. имејл адресе, правог имена)", + "right-viewmyprivateinfo": "преглед својих приватних података (нпр. имејл-адресу, право име)", + "right-editmyprivateinfo": "уређивање сопствених приватних података (нпр. имејл-адресе, правог имена)", "right-editmyoptions": "уређивање сопствених подешавања", "right-rollback": "брзо враћање измена последњег корисника који је мењао одређену страницу", "right-markbotedits": "означавање враћених измена као измене бота", @@ -1225,7 +1225,7 @@ "right-userrights": "уређивање свих корисничких права", "right-userrights-interwiki": "уређивање корисничких права на другим викијима", "right-siteadmin": "закључавање и откључавање базе података", - "right-override-export-depth": "извоз страница укључујући и повазене странице до дубине од пет веза", + "right-override-export-depth": "извоз страница укључујући и повазене странице до дубине од пет линкова", "right-sendemail": "слање имејла другим корисницима", "right-managechangetags": "прављење и (де)активирање [[Special:Tags|ознака]]", "right-applychangetags": "примењивање [[Special:Tags|ознака]] на нечије промене", @@ -1261,7 +1261,7 @@ "grant-uploadeditmovefile": "Отпремање, замена и премештање датотека", "grant-uploadfile": "Отпремање нових датотека", "grant-basic": "Основна права", - "grant-viewdeleted": "Преглед обрисаних страница и датотека", + "grant-viewdeleted": "Преглед избрисаних страница и датотека", "grant-viewmywatchlist": "Преглед вашег списак надгледања", "grant-viewrestrictedlogs": "Прегледање ограничених уноса у евиденцији", "newuserlogpage": "Евиденција нових корисника", @@ -1286,12 +1286,12 @@ "action-reupload-shared": "премостите ову датотеку са заједничког складишта", "action-upload_by_url": "отпремите ову датотеку путем УРЛ-а", "action-writeapi": "користите API за писање", - "action-delete": "обришете ову страницу", + "action-delete": "избришете ову страницу", "action-deleterevision": "бришете ревизије", "action-deletelogentry": "бришете уносе у евиденцијама", - "action-deletedhistory": "прегледате обрисану историју странице", - "action-deletedtext": "прегледате обрисани текст ревизије", - "action-browsearchive": "претражујете обрисане странице", + "action-deletedhistory": "прегледате избрисану историју странице", + "action-deletedtext": "прегледате избрисани текст ревизије", + "action-browsearchive": "претражујете избрисане странице", "action-undelete": "враћате странице", "action-suppressrevision": "прегледате и враћате сакривене ревизије", "action-suppressionlog": "прегледате ову приватну евиденције", @@ -1371,13 +1371,13 @@ "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": "Неисправан филтер", + "rcfilters-invalid-filter": "Неважећи филтер", "rcfilters-empty-filter": "Нема активних филтера. Сви доприноси су приказани.", "rcfilters-filterlist-title": "Филтери", "rcfilters-filterlist-whatsthis": "Како ово функционише?", @@ -1450,11 +1450,11 @@ "rcfilters-filter-lastrevision-label": "Последња измена", "rcfilters-filter-lastrevision-description": "Само најновија промена на страници.", "rcfilters-filter-previousrevision-label": "Није последња ревизија", - "rcfilters-filter-previousrevision-description": "Све промене које нису „последње ревизије“.", - "rcfilters-filter-excluded": "Изостављено", + "rcfilters-filter-previousrevision-description": "Све промене које нису „последње ревизије”.", + "rcfilters-filter-excluded": "Изузето", "rcfilters-tag-prefix-namespace-inverted": ":није $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": "Филтрирајте резултате према ознаци измене", @@ -1516,7 +1516,7 @@ "rc-old-title": "првобитно направљено као „$1“", "recentchangeslinked": "Сродне промене", "recentchangeslinked-feed": "Сродне измене", - "recentchangeslinked-toolbox": "Сродне измене", + "recentchangeslinked-toolbox": "Сродне промене", "recentchangeslinked-title": "Измене сродне са „$1“", "recentchangeslinked-summary": "Унесите име странице да бисте видели промене на страницама које су повезане са или са те странице. (Да бисте видели чланове категорије, унесите {{ns:category}}:Име категорије). Промене на страницама које су на [[Special:Watchlist|Вашем списку надгледања]] су подебљане.", "recentchangeslinked-page": "Назив странице:", @@ -1536,7 +1536,7 @@ "upload_directory_missing": "Фасцикла за слање ($1) недостаје и сервер је не може направити.", "upload_directory_read_only": "Сервер не може да пише по фасцикли за слање ($1).", "uploaderror": "Грешка при отпремању", - "upload-recreate-warning": "Упозорење: датотека с тим називом је обрисана или премештена.\n\nЕвиденција брисања и премештања се налази испод:", + "upload-recreate-warning": "Упозорење: Датотека са тим именом је избрисана или премештена.\n\nЕвиденција брисања и премештања странице наведена је испод са образложењем:", "uploadtext": "Користите образац испод да бисте отпремили датотеке.\nЗа преглед или претрагу постојећих датотека, погледајте [[Special:FileList|списак отпремљених датотека]], поновна отпремања су наведена у [[Special:Log/upload|евиденцији отпремања]], а брисања у [[Special:Log/delete|евиденцији брисања]].\n\nДатотеку додајете на жељену страницу користећи следеће обрасце:\n* '''[[{{ns:file}}:Слика.jpg]]''' за верзију слике у пуној величини\n* '''[[{{ns:file}}:Слика.png|200п|мини|лево|опис]]''' за верзију слике с величином од 200 пиксела која је приказана у засебном оквиру, заједно с описом.\n* '''[[{{ns:media}}:Датотека.ogg]]''' за директно повезивање с датотеком без њеног приказивања", "upload-permitted": "Дозвољени {{PLURAL:$2|тип|типови}} датотека: $1.", "upload-preferred": "Препоручени {{PLURAL:$2|тип|типови}} датотека: $1.", @@ -1552,7 +1552,7 @@ "ignorewarning": "Занемари упозорења и сачувај датотеку", "ignorewarnings": "Занемари сва упозорења", "minlength1": "Назив датотеке мора имати барем један знак.", - "illegalfilename": "Датотека „$1“ садржи знакове који нису дозвољени у називима страница.\nПромените назив датотеке и поново је пошаљите.", + "illegalfilename": "Име датотеке „$1“ садржи знакове који нису дозвољени у насловима страница.\nПреименујте датотеку и покушате да је поново отпремите.", "filename-toolong": "Називи датотека могу имати највише 240 бајтова.", "badfilename": "Име датотеке је промењено у „$1“.", "filetype-mime-mismatch": "Проширење датотеке „.$1“ не одговара препознатом типу MIME датотеке ($2).", @@ -1575,19 +1575,19 @@ "large-file": "Препоручљиво је да датотеке не буду веће од $1; ова датотека је $2.", "largefileserver": "Ова датотека прелази ограничење величине.", "emptyfile": "Датотека коју сте послали је празна.\nУзрок може бити грешка у називу датотеке.\nПроверите да ли заиста желите да је пошаљете.", - "windows-nonascii-filename": "Овај вики не подржава називе датотека с посебним знацима.", + "windows-nonascii-filename": "Овај вики не подржава имена датотека са посебним знацима.", "fileexists": "Датотека с овим именом већ постоји. Погледајте [[:$1]] ако нисте сигурни да ли желите да је промените.\n[[$1|thumb]]", "filepageexists": "Страница с описом ове датотеке је већ направљена овде [[:$1]], иако датотека не постоји.\nОпис који сте навели се неће појавити на страници с описом.\nДа би се ваш опис овде нашао, потребно је да га ручно измените.\n[[$1|thumb]]", "fileexists-extension": "Датотека са сличним називом већ постоји: [[$2|thumb]]\n* Назив датотеке коју шаљете: [[:$1]]\n* Назив постојеће датотеке: [[:$2]]\nДа ли желите да користите препознатљивије име?", "fileexists-thumbnail-yes": "Изгледа да је датотека умањено издање слике ''(thumbnail)''.\n[[$1|thumb]]\nПроверите датотеку [[:$1]].\nАко је проверена датотека иста слика оригиналне величине, није потребно слати додатну слику.", "file-thumbnail-no": "Датотека почиње са $1.\nИзгледа да се ради о умањеној слици ''(thumbnail)''.\nУколико имате ову слику у пуној величини, пошаљите је, а ако немате, промените назив датотеке.", "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": "Датотека је дупликат актуелне верзије [[:$1]].", "fileexists-duplicate-version": "Датотека је дупликат {{PLURAL:$2|старе верзије|старих верзија}} [[:$1]].", "file-exists-duplicate": "Ово је дупликат {{PLURAL:$1|следеће датотеке|следећих датотека}}:", - "file-deleted-duplicate": "Датотека истоветна овој ([[:$1]]) је претходно обрисана.\nПогледајте историју брисања пре поновног слања.", - "file-deleted-duplicate-notitle": "Датотека идентична овој претходно је обрисана и име јој је сакривено.\nТребали бисте питати некога ко може видети податке скривених датотека да прегледа ситуацију пре него што поново отпремите датотеку.", + "file-deleted-duplicate": "Датотека која је идентична овој ([[:$1]]) је раније била избрисана.\nТребате да проверите историју брисања те датотеке пре него што наставите са њеним поновним оптремањем.", + "file-deleted-duplicate-notitle": "Датотека која је идентична овој раније је избрисана и име јој је сакривено.\nТребате да питате некога ко може видети податке скривених датотека да прегледа ситуацију пре него што поново отпремите датотеку.", "uploadwarning": "Упозорење при отпремању", "uploadwarning-text": "Измените опис датотеке и покушајте поново.", "uploadwarning-text-nostash": "Ре-отпремите датотеку, измените опис испод и покушајте поново.", @@ -1616,10 +1616,10 @@ "upload-description": "Опис датотеке", "upload-options": "Опције отпремања", "watchthisupload": "Надгледај ову датотеку", - "filewasdeleted": "Датотека с овим називом је раније послата, али је обрисана.\nПроверите $1 пре него што наставите с поновним слањем.", + "filewasdeleted": "Датотека са овим именом је раније оптремљена и након тога избрисана.\nТребате да проверите $1 пре него што наставите са њеним поновним оптремањем.", "filename-bad-prefix": "Назив датотеке коју шаљете почиње са „$1“, а њега обично додељују дигитални фотоапарати.\nИзаберите назив датотеке који описује њен садржај.", "filename-prefix-blacklist": " #
\n# Синтакса је следећа:\n#   * Све од тарабе па до краја реда је коментар\n#   * Сваки ред означава префикс типичних назива датотека које додељивају дигитални апарати\nCIMG # Касио\nDSC_ # Никон\nDSCF # Фуџи\nDSCN # Никон\nDUW # неки мобилни телефони\nIMG # опште\nJD # Џеноптик\nMGP # Пентакс\nPICT # разно\n #
", - "upload-proto-error": "Неисправан протокол", + "upload-proto-error": "Неважећи протокол", "upload-proto-error-text": "Слање са спољне локације захтева адресу која почиње са http:// или ftp://.", "upload-file-error": "Унутрашња грешка", "upload-file-error-text": "Дошло је до унутрашње грешке при отварању привремене датотеке на серверу.\nКонтактирајте [[Special:ListUsers/sysop|администратора]].", @@ -1650,7 +1650,7 @@ "backend-fail-hashes": "Не могу да добијем дисперзије датотеке за упоређивање.", "backend-fail-notsame": "Већ постоји неистоветна датотека – $1.", "backend-fail-invalidpath": "$1 није важећа путања за складиштење.", - "backend-fail-delete": "Не могу да обришем датотеку „$1”.", + "backend-fail-delete": "Не могу да избришем датотеку „$1”.", "backend-fail-describe": "Не могу да променим метаподатке за датотеку „$1“.", "backend-fail-alreadyexists": "Датотека $1 већ постоји.", "backend-fail-store": "Не могу да сместим датотеку $1 у $2.", @@ -1673,7 +1673,7 @@ "filejournal-fail-dbquery": "Не могу да ажурирам новинарску базу за складишну основу „$1“.", "lockmanager-notlocked": "Не могу да откључам „$1“ јер није закључан.", "lockmanager-fail-closelock": "Не могу да затворим катанац за „$1“.", - "lockmanager-fail-deletelock": "Не могу да обришем катанац за „$1“.", + "lockmanager-fail-deletelock": "Не могу да избришем катанац за „$1“.", "lockmanager-fail-acquirelock": "Не могу да се закључам за „$1“.", "lockmanager-fail-openlock": "Не могу да отворим катанац за „$1“. Уверите се да је ваш директоријум за отпремање исправно конфигурисан и да ваш веб-сервер има дозволу да пише у том директоријуму. Погледајте https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgUploadDirectory за више информација.", "lockmanager-fail-releaselock": "Не могу да ослободим катанац за „$1“.", @@ -1712,18 +1712,18 @@ "uploadstash-no-such-key": "Нема таквог кључа ($1). Не могу уклонити.", "uploadstash-no-extension": "Додатак је празан.", "uploadstash-zero-length": "Датотека је празна", - "invalid-chunk-offset": "Неисправна полазна тачка", + "invalid-chunk-offset": "Неважећа полазна тачка", "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": "Захтевана путања није у подешеној фасцикли за отпремање.", - "img-auth-badtitle": "Не могу да направим валидан наслов за „$1“.", + "img-auth-notindir": "Тражена путања није у подешеном директоријуму за отпремање.", + "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": "Неисправна адреса: $1", + "http-invalid-url": "Неважећи URL: $1", "http-invalid-scheme": "Адресе са шемом „$1“ нису подржане.", "http-request-error": "HTTP захтев није прошао због непознате грешке.", "http-read-error": "HTTP грешка при читању.", @@ -1741,8 +1741,8 @@ "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“ није отворен.", @@ -1762,8 +1762,8 @@ "file-anchor-link": "Датотека", "filehist": "Историја датотеке", "filehist-help": "Кликните на датум/време да бисте видели тадашњу верзију датотеке.", - "filehist-deleteall": "обриши све", - "filehist-deleteone": "обриши", + "filehist-deleteall": "избриши све", + "filehist-deleteone": "избриши", "filehist-revert": "врати", "filehist-current": "актуелна", "filehist-datetime": "Датум/време", @@ -1776,9 +1776,9 @@ "filehist-comment": "Коментар", "imagelinks": "Употреба датотеке", "linkstoimage": "{{PLURAL:$1|Следећа страница користи|$1 следеће странице користе|$1 следећих страница користи}} ову датотеку:", - "linkstoimage-more": "Више од $1 {{PLURAL:$1|странице користи|странице користе|страница користе}} ову датотеку.\nСледећи списак приказује само {{PLURAL:$1|прву страницу која користи|прве $1 странице које користе|првих $1 страница које користе}} ову датотеку.\nДоступан је и [[Special:WhatLinksHere/$2|потпуни списак]].", + "linkstoimage-more": "Више од $1 {{PLURAL:$1|страница користи|странице користе|страница користи}} ову датотеку.\nСледећи списак приказује {{PLURAL:$1|прву страницу која користи|прве $1 странице које користе|првих $1 страница које користе}} само ову датотеку.\nДоступан је и [[Special:WhatLinksHere/$2|потпуни списак]].", "nolinkstoimage": "Нема страница које користе ову датотеку.", - "morelinkstoimage": "Погледајте [[Special:WhatLinksHere/$1|више веза]] до ове датотеке.", + "morelinkstoimage": "Погледајте [[Special:WhatLinksHere/$1|више линкова]] до ове датотеке.", "linkstoimage-redirect": "$1 (преусмерење датотеке) $2", "duplicatesoffile": "{{PLURAL:$1|Следећа датотека је дупликат|Следеће $1 датотеке су дупликати|Следећих $1 датотека су дупликати}} ове датотеке ([[Special:FileDuplicateSearch/$2|детаљније]]):", "sharedupload": "Ова датотека се налази на $1 и може се користити и на другим пројектима.", @@ -1787,7 +1787,7 @@ "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": "заједничко складиште", @@ -1803,14 +1803,14 @@ "filerevert-success": "Датотека [[Media:$1|$1]] је враћена на [$4 верзију од $2; $3].", "filerevert-badversion": "Не постоји ранија локална верзија ове датотеке са наведеном временском ознаком.", "filerevert-identical": "Актуелна верзија датотеке је индентична изабраној.", - "filedelete": "Обриши $1", - "filedelete-legend": "Обриши датотеку", + "filedelete": "Брисање датотеке/странице $1", + "filedelete-legend": "Избриши датотеку", "filedelete-intro": "Бришете датотеку '''[[Media:$1|$1]]''' заједно с њеном историјом.", "filedelete-intro-old": "Бришете верзију датотеке '''[[Media:$1|$1]]''' од [$4 $2; $3].", "filedelete-comment": "Разлог:", - "filedelete-submit": "Обриши", - "filedelete-success": "Датотека '''$1''' је обрисана.", - "filedelete-success-old": "Верзија датотеке [[Media:$1|$1]] од $2, $3 је обрисана.", + "filedelete-submit": "Избриши", + "filedelete-success": "Датотека $1 је избрисана.", + "filedelete-success-old": "Верзија датотеке [[Media:$1|$1]] од $2, $3 је избрисана.", "filedelete-nofile": "Датотека '''$1''' не постоји.", "filedelete-nofile-old": "Не постоји архивирана верзија датотеке $1 са наведеним особинама.", "filedelete-otherreason": "Други/додатни разлог:", @@ -1818,7 +1818,7 @@ "filedelete-reason-dropdown": "*Најчешћи разлози брисања\n** Кршење ауторских права\n** Дупликати датотека", "filedelete-edit-reasonlist": "Уреди разлоге брисања", "filedelete-maintenance": "Брисање и враћање датотека је привремено онемогућено због одржавања.", - "filedelete-maintenance-title": "Не могу да обришем датотеку", + "filedelete-maintenance-title": "Не могу да избришем датотеку", "mimesearch": "MIME претрага", "mimesearch-summary": "Ова страница омогућава филтрирање датотека према њиховим MIME типовима.\nУлазни подаци: contenttype/subtype или contenttype/*, нпр. image/jpeg.", "mimetype": "MIME тип:", @@ -1830,7 +1830,7 @@ "listduplicatedfiles-entry": "[[:File:$1|$1]] има [[$3|{{PLURAL:$2|један дупликат|$2 дупликата}}]].", "unusedtemplates": "Некоришћени шаблони", "unusedtemplatestext": "Ова страница наводи све странице у именском простору {{ns:template}} које нису укључене ни на једној другој страници.\nПре брисања проверите да ли друге странице воде до тих шаблона.", - "unusedtemplateswlh": "остале везе", + "unusedtemplateswlh": "остали линкови", "randompage": "Случајна страница", "randompage-nopages": "Нема страница у {{PLURAL:$2|следећем именском простору|следећим именским просторима}}: $1.", "randomincategory": "Случајна страница у категорији", @@ -1847,9 +1847,9 @@ "statistics-header-users": "Корисници", "statistics-header-hooks": "Остало", "statistics-articles": "Странице са садржајем", - "statistics-pages": "Страница", + "statistics-pages": "Странице", "statistics-pages-desc": "Све странице на викију, укључујући странице за разговор, преусмерења итд.", - "statistics-files": "Број послатих датотека", + "statistics-files": "Отпремљене датотеке", "statistics-edits": "Број измена страница откад постоји {{SITENAME}}", "statistics-edits-average": "Просечан број измена по страници", "statistics-users": "Регистровани корисници", @@ -1865,23 +1865,23 @@ "pageswithprop-prophidden-long": "сакривено дуго текстуално својство ($1)", "pageswithprop-prophidden-binary": "сакривено дуго бинарно својство ($1)", "doubleredirects": "Двострука преусмерења", - "doubleredirectstext": "Ова страница приказује странице које преусмеравају на друга преусмерења.\nСваки ред садржи везе према првом и другом преусмерењу, као и одредишну страницу другог преусмерења која је обично „прави“ чланак на кога прво преусмерење треба да упућује.\nПрецртани уноси су већ решени.", + "doubleredirectstext": "Ова страница приказује странице које преусмеравају на друга преусмерења.\nСваки ред садржи линкове према првом и другом преусмерењу, као и одредишну страницу другог преусмерења која је обично „прави“ чланак на кога прво преусмерење треба да упућује.\nПрецртани уноси су већ решени.", "double-redirect-fixed-move": "[[$1]] је премештен.\nАутоматски је ажурирано и сада преусмерава на [[$2]].", "double-redirect-fixed-maintenance": "Аутоматски исправља двострука преусмерења из [[$1]] у [[$2]] као део одржавања", "double-redirect-fixer": "Исправљач преусмерења", "brokenredirects": "Покварена преусмерења", - "brokenredirectstext": "Следећа преусмерења упућују на непостојеће странице:", + "brokenredirectstext": "Следећа преусмерења воде на непостојеће странице:", "brokenredirects-edit": "уреди", - "brokenredirects-delete": "обриши", - "withoutinterwiki": "Странице без језичких веза", - "withoutinterwiki-summary": "Следеће странице нису повезане с другим језицима.", + "brokenredirects-delete": "избриши", + "withoutinterwiki": "Странице без језичких линкова", + "withoutinterwiki-summary": "Следеће странице немају линкове према верзијама на другим језицима.", "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|ревизија|ревизије|ревизија}}", @@ -1898,18 +1898,18 @@ "unusedimages": "Некоришћене датотеке", "wantedcategories": "Тражене категорије", "wantedpages": "Тражене странице", - "wantedpages-summary": "Списак непостојећих страница са највише веза до њих, на овом списку се не налазе странице до којих воде преусмерења. За списак покварених преусмерења погледајте [[{{#special:BrokenRedirects}}|списак покварених преусмерења]].", - "wantedpages-badtitle": "Неисправан наслов у сету резултата: $1", + "wantedpages-summary": "Списак непостојећих страница са највише линкова до њих, на овом списку се не налазе странице до којих воде преусмерења. За списак покварених преусмерења погледајте [[{{#special:BrokenRedirects}}|списак покварених преусмерења]].", + "wantedpages-badtitle": "Невалидан наслов у скупу резултата: $1", "wantedfiles": "Тражене датотеке", "wantedfiletext-cat": "Следеће датотеке се користе, али не постоје. Датотеке из других ризница могу бити наведене иако не постоје. Такве датотеке ће бити поништене са списка. Поред тога, странице које садрже непостојеће датотеке се налазе [[:$1|овде]].", "wantedfiletext-nocat": "Следеће датотеке се користе, али не постоје. Датотеке из других ризница могу бити наведене иако не постоје. Такве датотеке ће бити поништене са списка.", "wantedfiletext-nocat-noforeign": "Следеће датотеке се користе, али не постоје.", "wantedtemplates": "Тражени шаблони", - "mostlinked": "Странице са највише веза", - "mostlinkedcategories": "Категорије са највише веза", - "mostlinkedtemplates": "Странице са највише веза", + "mostlinked": "Странице са највише линкова", + "mostlinkedcategories": "Категорије са највише линкова", + "mostlinkedtemplates": "Странице са највише линкова", "mostcategories": "Странице са највише категорија", - "mostimages": "Датотеке са највише веза", + "mostimages": "Датотеке са највише линкова", "mostinterwikis": "Странице са највише међувикија", "mostrevisions": "Странице са највише ревизија", "prefixindex": "Све странице са префиксом", @@ -1919,7 +1919,7 @@ "shortpages": "Кратке странице", "longpages": "Дугачке странице", "deadendpages": "Ћорсокаци", - "deadendpagestext": "Следеће странице немају везе до других страница на овом викију.", + "deadendpagestext": "Следеће странице немају линкове до других страница на овом викију.", "protectedpages": "Заштићене странице", "protectedpages-filters": "Филтери:", "protectedpages-indef": "Само неограничене заштите", @@ -1986,7 +1986,7 @@ "apisandbox-submit-invalid-fields-title": "Нека поља нису валидна", "apisandbox-submit-invalid-fields-message": "Молимо Вас поправите означена поља и покушајте поново.", "apisandbox-results": "Резултати", - "apisandbox-sending-request": "Слање API захтева...", + "apisandbox-sending-request": "Шаљем API захтев…", "apisandbox-loading-results": "Пријем API резултата...", "apisandbox-results-error": "Дошло је до грешке приликом учитавања резултата API упита: $1.", "apisandbox-request-selectformat-label": "Прикажи сахтеване податке као:", @@ -1996,8 +1996,8 @@ "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": "Унесите max да би сте користили највеће ограничење.", @@ -2007,16 +2007,16 @@ "booksources-search-legend": "Претражи штампане изворе", "booksources-isbn": "ISBN:", "booksources-search": "Претражи", - "booksources-text": "Испод се налази списак веза ка сајтовима који се баве продајом нових и половних књига, а који би могли имати додатне податке о књигама које тражите:", + "booksources-text": "Испод се налази списак линкова ка сајтовима који се баве продајом нових и половних књига, а који би могли имати додатне податке о књигама које тражите:", "booksources-invalid-isbn": "Наведени ISBN број није валидан. Проверите да није дошло до грешке при копирању из првобитног извора.", - "magiclink-tracking-rfc": "Странице с магичним RFC везама", - "magiclink-tracking-pmid": "Странице с магичним PMID везама", - "magiclink-tracking-isbn": "Странице са ISBN магичним везама", + "magiclink-tracking-rfc": "Странице са магичним RFC линковима", + "magiclink-tracking-pmid": "Странице са магичним PMID линковима", + "magiclink-tracking-isbn": "Странице са ISBN магичним линковима", "specialloguserlabel": "Извршилац:", "speciallogtitlelabel": "Циљ (наслов или {{ns:user}}:корисничко име):", "log": "Евиденције", "logeventslist-submit": "Прикажи", - "logeventslist-more-filters": "Приказ додатних дневника:", + "logeventslist-more-filters": "Приказ додатних евиденција:", "logeventslist-patrol-log": "Евиденција патролирања", "logeventslist-tag-log": "Евиденција ознака", "all-logs-page": "Све јавне евиденције", @@ -2048,15 +2048,15 @@ "categories-submit": "Прикажи", "categoriespagetext": "{{PLURAL:$1|1=Следећа категорија садржи|Следеће категорије садрже}} странице или датотеке.\n[[Special:UnusedCategories|Некоришћене категорије]] нису приказане овде.\nПогледајте и [[Special:WantedCategories|тражене категорије]].", "categoriesfrom": "Прикажи категорије почев од:", - "deletedcontributions": "Обрисани кориснички доприноси", - "deletedcontributions-title": "Обрисани кориснички доприноси", + "deletedcontributions": "Избрисани кориснички доприноси", + "deletedcontributions-title": "Избрисани кориснички доприноси", "sp-deletedcontributions-contribs": "доприноси", - "linksearch": "Претрага спољашњих веза", + "linksearch": "Претрага спољашњих линкова", "linksearch-pat": "Образац претраге:", "linksearch-ns": "Именски простор:", "linksearch-ok": "Претражи", "linksearch-text": "Могу се користити џокери попут „*.wikipedia.org“.\nПотребан је највиши домен, као „*.org“.
\n{{PLURAL:$2|1=Подржан протокол|Подржани протоколи}}: $1 (задаје http:// ако не наведете протокол).", - "linksearch-line": "$1 веза у $2", + "linksearch-line": "$1 води са $2", "linksearch-error": "Џокери се могу појавити само на почетку адресе.", "listusersfrom": "Прикажи кориснике почев од:", "listusers-submit": "Прикажи", @@ -2101,7 +2101,7 @@ "restricted-displaytitle-ignored": "Странице са занемареним насловима за приказ", "noindex-category-desc": "Странице које у себи имају магичну реч __NOINDEX__.", "index-category-desc": "Странице које у себи имају магичну реч __INDEX__ и самим тим су индексиране од стране робота.", - "broken-file-category-desc": "Странице које имају везе до непостојећих датотека.", + "broken-file-category-desc": "Страница садржи покварени линк до датотеке (линк за уграђивање датотеке када она не постоји).", "hidden-category-category-desc": "Категорије које у себи имају магичну реч __HIDDENCAT__ и самим тим се не приказују у одељку за категорије на страницама.", "trackingcategories-nodesc": "Опис није доступан.", "trackingcategories-disabled": "Категорија је онемогућена", @@ -2114,10 +2114,10 @@ "defemailsubject": "{{SITENAME}} — Имејл од {{GENDER:$1|корисника|кориснице}} „$1”", "usermaildisabled": "Кориснички имејл је онемогућен", "usermaildisabledtext": "Не можете да шаљете имејлове другим корисницима на овом викију", - "noemailtitle": "Нема имејл адресе", - "noemailtext": "Овај корисник није навео валидну имејл адресу.", + "noemailtitle": "Нема имејл-адресе", + "noemailtext": "Овај корисник није навео важећу имејл-адресу.", "nowikiemailtext": "Овај корисник је одлучио да не прима имејлове од других корисника.", - "emailnotarget": "Непостојеће или неисправно корисничко име примаоца.", + "emailnotarget": "Непостојеће или наважеће корисничко име примаоца.", "emailtarget": "Унос корисничког имена примаоца", "emailusername": "Корисничко име:", "emailusernamesubmit": "Пошаљи", @@ -2153,7 +2153,7 @@ "unwatch": "Прекини надгледање", "unwatchthispage": "Прекини надгледање", "notanarticle": "Није страница са садржајем", - "notvisiblerev": "Последња ревизија другог корисника је обрисана.", + "notvisiblerev": "Последња ревизија другог корисника је избрисана.", "watchlist-details": "Имате {{PLURAL:$1|$1 страницу|$1 странице|$1 страница}} на свом списку надгледања (плус странице за разговор).", "wlheader-enotif": "Обавештење имејлом је омогућено.", "wlheader-showupdated": "Странице које су промењене откад сте их последњи пут посетили су подебљане.", @@ -2192,19 +2192,19 @@ "enotif_minoredit": "Ово је мања измена", "created": "направљена", "changed": "измењена", - "deletepage": "Обриши страницу", + "deletepage": "Избриши страницу", "confirm": "Потврди", "excontent": "садржај је био: „$1“", "excontentauthor": "садржај је био: „$1“, а једини уредник „[[Special:Contributions/$2|$2]]“ ([[User talk:$2|разговор]])", "exbeforeblank": "садржај пре брисања је био: „$1“", "delete-confirm": "Брисање странице „$1“", "delete-legend": "Брисање", - "historywarning": "Упозорење: страница коју желите да обришете има историју са $1 {{PLURAL:$1|ревизијом|ревизије|ревизија}}:", + "historywarning": "Упозорење: Страница коју желите да избришете има историју са $1 {{PLURAL:$1|ревизијом|ревизије|ревизија}}:", "historyaction-submit": "Прикажи", - "confirmdeletetext": "Управо ћете обрисати страницу, укључујући и њену историју.\nПотврдите своју намеру, да разумете последице и да ово радите у складу с [[{{MediaWiki:Policy-url}}|правилима]].", + "confirmdeletetext": "Управо ћете избрисати страницу, укључујући и њену историју.\nПотврдите своју намеру, да разумете последице и да ово радите у складу са [[{{MediaWiki:Policy-url}}|правилима]].", "actioncomplete": "Радња је завршена", "actionfailed": "Радња није успела", - "deletedtext": "Страница „$1“ је обрисана.\nПогледајте ''$2'' за запис недавних брисања.", + "deletedtext": "Страница „$1“ је избрисана.\nПогледајте $2 за запис недавних брисања.", "dellogpage": "Евиденција брисања", "dellogpagetext": "Испод је списак недавних брисања.", "deletionlog": "евиденција брисања", @@ -2219,9 +2219,9 @@ "delete-edit-reasonlist": "Уреди разлоге брисања", "delete-toobig": "Ова страница има велику историју измена, преко $1 {{PLURAL:$1|ревизија|ревизије|ревизија}}.\nБрисање таквих страница је ограничено да би се спречило случајно оптерећење сервера.", "delete-warning-toobig": "Ова страница има велику историју измена, преко $1 {{PLURAL:$1|ревизија|ревизије|ревизија}}.\nЊено брисање може да поремети базу података, стога поступајте с опрезом.", - "deleteprotected": "Не можете обрисати ову страницу зато што је заштићена.", + "deleteprotected": "Не можете да избришете ову страницу јер је заштићена.", "deleting-backlinks-warning": "Упозорење: бришете страницу која је укључена у [[Special:WhatLinksHere/{{FULLPAGENAME}}|друге странице]] или друге странице воде на њу.", - "deleting-subpages-warning": "Пажња: Страницу коју желите обрисати има [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|подстраницу|$1 подстранице|$1 подстраница|51=преко 50 подстраница}}]].", + "deleting-subpages-warning": "Упозорење: Страница коју желите избрисати има [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|подстраницу|$1 подстранице|$1 подстраница|51=преко 50 подстраница}}]].", "rollback": "Врати измене", "rollbacklink": "врати", "rollbacklinkcount": "врати $1 {{PLURAL:$1|измену|измене|измена}}", @@ -2275,7 +2275,7 @@ "protect-legend": "Подешавања заштите", "protectcomment": "Разлог:", "protectexpiry": "Истиче:", - "protect_expiry_invalid": "Време истека није важеће.", + "protect_expiry_invalid": "Време истека је неважеће.", "protect_expiry_old": "Време истека је у прошлости.", "protect-unchain-permissions": "Откључај даљња подешавања заштите", "protect-text": "Овде можете да погледате и промените ниво заштите странице $1.", @@ -2314,19 +2314,19 @@ "restriction-level-sysop": "потпуно заштићено", "restriction-level-autoconfirmed": "полузаштићено", "restriction-level-all": "сви нивои", - "undelete": "Преглед обрисаних страница", - "undeletepage": "Преглед и враћање обрисаних страница", - "undeletepagetitle": "Следећи садржај се састоји од обрисаних ревизија странице [[:$1|$1]].", - "viewdeletedpage": "Приказ обрисаних страница", - "undeletepagetext": "{{PLURAL:$1|Следећа страница је обрисана, али је још у архиви и може бити враћена|Следеће $1 странице су обрисане, али су још у архиви и могу бити враћене|Следећих $1 страница је обрисано, али су још у архиви и могу бити враћене}}.\nАрхива се повремено чисти од оваквих страница.", + "undelete": "Преглед избрисаних страница", + "undeletepage": "Преглед и враћање избрисаних страница", + "undeletepagetitle": "Следећи садржај се састоји од избрисаних ревизија странице [[:$1|$1]].", + "viewdeletedpage": "Преглед избрисаних страница", + "undeletepagetext": "{{PLURAL:$1|Следећа страница је избрисана, али је још у архиви и може бити враћена|Следеће $1 странице су избрисане, али су још у архиви и могу бити враћене|Следећих $1 страница је избрисано, али су још у архиви и могу бити враћене}}.\nАрхива се повремено чисти од оваквих страница.", "undelete-fieldset-title": "Враћање ревизија", "undeleteextrahelp": "Да бисте вратили целу историју странице, оставите све кућице неозначене и кликните на дугме {{int:undeletebtn}}.\nАко желите да вратите одређене ревизије, означите их и кликните на {{int:undeletebtn}}.", - "undeleterevisions": "{{PLURAL:$1|Измена}} обрисано: $1", + "undeleterevisions": "{{PLURAL:$1|Избрисана је|Избрисане су|Избрисано је}} $1 {{PLURAL:$1|ревизија|ревизије|ревизија}}", "undeletehistory": "Ако вратите страницу, све ревизије ће бити враћене њеној историји.\nАко је у међувремену направљена нова страница с истим називом, враћене ревизије ће се појавити у њеној ранијој историји.", - "undeleterevdel": "Враћање неће бити извршено ако је резултат тога делимично брисање последње ревизије.\nУ таквим случајевима морате искључити или открити најновије обрисане ревизије.", - "undeletehistorynoadmin": "Ова страница је обрисана.\nРазлог за брисање се налази испод, заједно с детаљима о кориснику који је уредио ову страницу пре брисања.\nТекст обрисаних ревизија је доступан само администраторима.", - "undelete-revision": "Обрисана измена странице $1 (дана $4; $5) од стране {{GENDER:$3|корисника|кориснице|корисника}} $3:", - "undeleterevision-missing": "Неважећа или недостајућа ревизија.\nМожда сте унели погрешну везу или је ревизија враћена или уклоњена из архиве.", + "undeleterevdel": "Враћање неће бити извршено ако је резултат тога делимично брисање последње ревизије.\nУ таквим случајевима морате искључити или открити најновије избрисане ревизије.", + "undeletehistorynoadmin": "Ова страница је избрисана.\nРазлог за брисање се налази испод, заједно са детаљима о кориснику који је уредио ову страницу пре брисања.\nТекст избрисаних ревизија је доступан само администраторима.", + "undelete-revision": "Избрисана ревизија странице $1 (дана $4; $5) од стране {{GENDER:$3|корисника|кориснице}} $3:", + "undeleterevision-missing": "Неважећа или недостајућа ревизија.\nМожда сте унели лош линк или је ревизија враћена или уклоњена из архиве.", "undeleterevision-duplicate-revid": "Не могу вратити {{PLURAL:$1|ревизију|$1 ревизије|$1 ревизија}} јер се {{PLURAL:$1|њен|њихов}} rev_id већ користи.", "undelete-nodiff": "Претходне измене нису пронађене.", "undeletebtn": "Врати", @@ -2336,9 +2336,9 @@ "undeletecomment": "Разлог:", "cannotundelete": "Враћање једне или свих није успело:\n$1", "undeletedpage": "Страница $1 је враћена\n\nПогледајте [[Special:Log/delete|евиденцију брисања]] за записе о недавним брисањима и враћањима.", - "undelete-header": "Погледајте [[Special:Log/delete|евиденцију брисања]] за недавно обрисане странице.", - "undelete-search-title": "Претрага обрисаних страница", - "undelete-search-box": "Претражи обрисане странице", + "undelete-header": "Погледајте [[Special:Log/delete|евиденцију брисања]] за недавно избрисане странице.", + "undelete-search-title": "Претрага избрисаних страница", + "undelete-search-box": "Претрага избрисаних страница", "undelete-search-prefix": "Прикажи странице које почињу са:", "undelete-search-full": "Прикажи наслове који садрже:", "undelete-search-submit": "Претражи", @@ -2347,15 +2347,15 @@ "undelete-bad-store-key": "Не могу да вратим измену датотеке од $1: датотека је недостајала пре брисања.", "undelete-cleanup-error": "Грешка при брисању некоришћене архиве „$1“.", "undelete-missing-filearchive": "Не могу да вратим архиву с ИБ $1 јер се она не налази у бази података.\nМожда је већ била враћена.", - "undelete-error": "Дошло је до грешке при враћању обрисане странице", + "undelete-error": "Дошло је до грешке при враћању избрисане странице", "undelete-error-short": "Грешка при враћању датотеке: $1", "undelete-error-long": "Дошло је до грешке при враћању датотеке:\n\n$1", - "undelete-show-file-confirm": "Да ли сте сигурни да желите да погледате обрисану измену датотеке „$1“ од $2 у $3?", + "undelete-show-file-confirm": "Јесте ли сигурни да желите да погледате избрисану ревизију датотеке „$1“ од $2 у $3?", "undelete-show-file-submit": "Да", "namespace": "Именски простор:", "invert": "Обрни избор", "tooltip-invert": "Означите ову кутијуцу да бисте сакрили промене на страницана у изабраном именском простору (и повезаним именским просторима, ако је означено)", - "tooltip-whatlinkshere-invert": "Означите ову кутијицу за сакривање веза са страница у изабраном именском простору.", + "tooltip-whatlinkshere-invert": "Означите ову кутијицу за сакривање линкова са страница у изабраном именском простору.", "namespace_association": "Повезани именски простор", "tooltip-namespace_association": "Означите ову кутијицу да бисте укључили и разговор или именски простор теме која је повезана са изабраним именским простором", "blanknamespace": "(главни)", @@ -2374,8 +2374,8 @@ "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": "разговор", @@ -2389,21 +2389,21 @@ "sp-contributions-hideminor": "Сакриј мање измене", "sp-contributions-submit": "Претражи", "whatlinkshere": "Шта води овде", - "whatlinkshere-title": "Странице које су повезане са „$1”", + "whatlinkshere-title": "Странице које воде на страницу „$1”", "whatlinkshere-page": "Страница:", - "linkshere": "Следеће странице имају везу до $2:", + "linkshere": "Следеће странице воде на страницу $2:", "nolinkshere": "Ниједна страница није повезана са: $2.", - "nolinkshere-ns": "Ниједна страница не води до '''$2''' у изабраном именском простору.", + "nolinkshere-ns": "Ниједна страница не води на страницу $2 у изабраном именском простору.", "isredirect": "преусмерење", "istemplate": "укључивање", - "isimage": "веза до датотеке", + "isimage": "линк до датотеке", "whatlinkshere-prev": "{{PLURAL:$1|претходни|претходна $1|претходних $1}}", "whatlinkshere-next": "{{PLURAL:$1|следећи|следећа $1|следећих $1}}", - "whatlinkshere-links": "← везе", + "whatlinkshere-links": "← линкови", "whatlinkshere-hideredirs": "$1 преусмерења", "whatlinkshere-hidetrans": "$1 укључивања", - "whatlinkshere-hidelinks": "$1 везе", - "whatlinkshere-hideimages": "$1 везе до датотеке", + "whatlinkshere-hidelinks": "$1 линкове", + "whatlinkshere-hideimages": "$1 линкова до датотеке", "whatlinkshere-filters": "Филтери", "whatlinkshere-submit": "Иди", "autoblockid": "Аутоматско блокирање #$1", @@ -2414,7 +2414,7 @@ "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": "Спречи корисника да шаље имејлове", @@ -2539,8 +2539,8 @@ "lockedbyandtime": "(од $1 дана $2 у $3)", "move-page": "Премештање „$1”", "move-page-legend": "Премештање странице", - "movepagetext": "Доњи образац ће преименовати страницу, премештајући целу историју на ново име.\nСтари наслов постаће преусмерење на нови.\nМожете ажурирати преусмерења која воде до изворног наслова;\nпогледајте [[Special:DoubleRedirects|двострука]] или [[Special:BrokenRedirects|покварена]] преусмерења.\nНа вама је одговорност да везе и даље иду тамо где треба.\n\nСтраница неће бити премештена ако већ постоји страница с тим именом (осим ако је празна, садржи преусмерење или нема историју измена).\nТо значи да можете вратити страницу на претходно име ако погрешите, али не можете ''преписати'' постојећу.\n\nНапомена:\nОво може представљати драстичну и неочекивану измену за популарну страницу;\nдобро размислите о последицама пре него што наставите.", - "movepagetext-noredirectfixer": "Доњи образац ће преименовати страницу, премештајући целу историју на ново име.\nСтари наслов постаће преусмерење на нови.\nПогледајте [[Special:DoubleRedirects|двострука]] или [[Special:BrokenRedirects|покварена]] преусмерења.\nНа вама је одговорност да везе и даље иду тамо где треба.\n\nСтраница неће бити премештена ако већ постоји страница с тим именом (осим ако је празна, садржи преусмерење или нема историју измена).\nТо значи да можете вратити страницу на претходно име ако погрешите, али не можете ''преписати'' постојећу.\n\nНапомена:\nОво може представљати драстичну и неочекивану измену за популарну страницу;\nдобро размислите о последицама пре него што наставите.", + "movepagetext": "Доњи образац ће преименовати страницу, премештајући целу историју на ново име.\nСтари наслов постаће преусмерење на нови.\nМожете ажурирати преусмерења која воде до изворног наслова;\nпогледајте [[Special:DoubleRedirects|двострука]] или [[Special:BrokenRedirects|покварена]] преусмерења.\nНа вама је одговорност да линкови и даље иду тамо где треба.\n\nСтраница неће бити премештена ако већ постоји страница с тим именом (осим ако је празна, садржи преусмерење или нема историју измена).\nТо значи да можете вратити страницу на претходно име ако погрешите, али не можете ''преписати'' постојећу.\n\nНапомена:\nОво може представљати драстичну и неочекивану измену за популарну страницу;\nдобро размислите о последицама пре него што наставите.", + "movepagetext-noredirectfixer": "Доњи образац ће преименовати страницу, премештајући целу историју на ново име.\nСтари наслов постаће преусмерење на нови.\nПогледајте [[Special:DoubleRedirects|двострука]] или [[Special:BrokenRedirects|покварена]] преусмерења.\nНа вама је одговорност да линкови и даље иду тамо где треба.\n\nСтраница неће бити премештена ако већ постоји страница с тим именом (осим ако је празна, садржи преусмерење или нема историју измена).\nТо значи да можете вратити страницу на претходно име ако погрешите, али не можете ''преписати'' постојећу.\n\nНапомена:\nОво може представљати драстичну и неочекивану измену за популарну страницу;\nдобро размислите о последицама пре него што наставите.", "movepagetalktext": "Ако сте означили овај квадратић, одговарајућа страница за разговор биће аутоматски премештена на нови наслов, осим ако већ постоји страница за разговор са истим насловом.\n\nУ том случају, мораћете ручно да је преместите или спојите, ако има потребе за тим.", "moveuserpage-warning": "'''Упозорење:''' на путу сте да преместите корисничку страницу. Имајте у виду да ће само страница бити премештена, а сам корисник ''неће'' бити преименован.", "movecategorypage-warning": "Упозорење: премештате страницу категорије. Имајте на уму да ће само страница бити премештена и да све странице у старој категорији неће бити рекатегорисане у нову категорију.", @@ -2560,7 +2560,7 @@ "movepage-moved": "'''„$1“ је премештена на „$2“'''", "movepage-moved-redirect": "Преусмерење је направљено.", "movepage-moved-noredirect": "Стварање преусмерења је онемогућено.", - "articleexists": "Страница с тим именом већ постоји или име које сте одабрали није важеће.\nОдаберите друго.", + "articleexists": "Страница са тим именом већ постоји или име које сте одабрали није важеће.\nОдаберите друго.", "cantmove-titleprotected": "Не можете да преместите страницу на то место јер је жељени наслов заштићен од стварања", "movetalk": "Премести и страницу за разговор", "move-subpages": "Премести и подстранице (до $1)", @@ -2576,13 +2576,13 @@ "movenosubpage": "Ова страница нема подстрана.", "movereason": "Разлог:", "revertmove": "врати", - "delete_and_move_text": "Одредишна страница „[[:$1]]“ већ постоји. \nЖелите ли да је обришете да бисте ослободили место за премештање?", - "delete_and_move_confirm": "Да, обриши страницу", - "delete_and_move_reason": "Обрисано да се ослободи место за премештање из „[[$1]]“", + "delete_and_move_text": "Одредишна страница „[[:$1]]“ већ постоји. \nЖелите ли да је избришете да бисте ослободили место за премештање?", + "delete_and_move_confirm": "Да, избриши страницу", + "delete_and_move_reason": "Избрисано да се ослободи место за премештање из „[[$1]]“", "selfmove": "Наслов је истоветан;\nне можете преместити страницу преко саме себе.", "immobile-source-namespace": "Не могу преместити странице у именски простор „$1“.", "immobile-target-namespace": "Не могу преместити странице у именски простор „$1“.", - "immobile-target-namespace-iw": "Међувики веза није валидно одредиште за премештање странице.", + "immobile-target-namespace-iw": "Међувики линк није важеће одредиште за премештање странице.", "immobile-source-page": "Ова страница се не може преместити.", "immobile-target-page": "Не могу да преместим на жељени наслов.", "bad-target-model": "Жељено одредиште користи другачији модел садржаја. Не могу да претворим из $1 у $2.", @@ -2597,7 +2597,7 @@ "move-over-sharedrepo": "[[:$1]] се налази на дељеном складишту. Ако преместите датотеку на овај наслов, то ће заменити дељену датотеку.", "file-exists-sharedrepo": "Наведени назив датотеке се већ користи у дељеном складишту.\nИзаберите други назив.", "export": "Извоз страница", - "exporttext": "Можете да извезете текст и историју измена одређене странице или скупа страница уклљених у XML формату.\nОво онда може да буде увезено у други вики који користи Медијавики софтвер преко [[Special:Import|странице за увоз]].\n\nДа бисте извезли странице, унесите називе у оквиру испод, с једним насловом по реду, и изаберите да ли желите актуелну ревизију и све остале, или само актуелну ревизију с подацима о последњој измени.\n\nУ другом случају, можете користити и везу, на пример [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] за страницу [[{{MediaWiki:Mainpage}}]].", + "exporttext": "Можете да извезете текст и историју измена одређене странице или скупа страница уклљених у XML формату.\nОво онда може да буде увезено у други вики који користи Медијавики софтвер преко [[Special:Import|странице за увоз]].\n\nДа бисте извезли странице, унесите називе у оквиру испод, с једним насловом по реду, и изаберите да ли желите актуелну ревизију и све остале, или само актуелну ревизију с подацима о последњој измени.\n\nУ другом случају, можете користити и линк, на пример [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] за страницу [[{{MediaWiki:Mainpage}}]].", "exportall": "Извези све странице", "exportcuronly": "Укључи само актуелну ревизију, не целу историју", "exportnohistory": "----\n'''Напомена:''' извоз пуне историје страница преко овог обрасца је онемогућено из техничких разлога.", @@ -2634,7 +2634,7 @@ "djvu_no_xml": "Не могу да преузмем XML за DjVu датотеку.", "thumbnail-temp-create": "Не могу да направим привремену датотеку минијатуре", "thumbnail-dest-create": "Не могу да сачувам минијатуру у одредишту", - "thumbnail_invalid_params": "Неисправни параметри за минијатуру", + "thumbnail_invalid_params": "Неважећи параметри сличице", "thumbnail_toobigimagearea": "Датотека са величинама већим од $1", "thumbnail_dest_directory": "Не могу да направим одредишну фасциклу", "thumbnail_image-type": "Тип слике није подржан", @@ -2656,7 +2656,7 @@ "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": "Нема страница за увоз.", @@ -2665,7 +2665,7 @@ "importunknownsource": "Непознат изворни тип увоза", "importnoprefix": "Није наведен међувики префикс", "importcantopen": "Не могу да отворим датотеку за увоз.", - "importbadinterwiki": "Неисправна међувики веза", + "importbadinterwiki": "Лош међувики линк", "importsuccess": "Увожење је завршено!", "importnosources": "Није одређен ниједан извор за увоз, тако да је отпремање историје онемогућено.", "importnofile": "Увозна датотека није послата.", @@ -2683,10 +2683,10 @@ "import-error-create": "Страница „$1“ није увезена јер вам није дозвољено да је направите.", "import-error-interwiki": "Не могу да увезем страницу „$1“ јер је њен назив резервисан за спољно повезивање (међувики).", "import-error-special": "Не могу да увезем страницу „$1“ јер она припада посебном именском простору које не прихвата странице.", - "import-error-invalid": "Не могу да увезем страницу „$1“ јер је њен назив неисправан.", + "import-error-invalid": "Страница „$1“ није увезена јер је име под којим се треба увости неважеће на овом викију.", "import-error-unserialize": "Не могу да десеријализујем ревизију $2 странице $1. Записано је да ревизија користи $3 модел садржаја у $4 формату.", "import-options-wrong": "{{PLURAL:$2|Погрешна опција|Погрешне опције}}: $1", - "import-rootpage-invalid": "Наведена основна страница има неисправан наслов.", + "import-rootpage-invalid": "Наведена основна страница има неважећи наслов.", "import-rootpage-nosubpage": "Именски простор „$1“ основне странице не дозвољава подстранице.", "importlogpage": "Евиденција увоза", "importlogpagetext": "Административни увози страница с историјама измена с других викија.", @@ -2714,8 +2714,8 @@ "tooltip-ca-history": "Претходне ревизије ове странице", "tooltip-ca-protect": "Заштитите ову страницу", "tooltip-ca-unprotect": "Промени заштиту ове странице", - "tooltip-ca-delete": "Обришите ову страницу", - "tooltip-ca-undelete": "Врати измене направљене на овој страници пре него што буде обрисана", + "tooltip-ca-delete": "Избришите ову страницу", + "tooltip-ca-undelete": "Врати измене које су начињене на овој страници пре брисања странице", "tooltip-ca-move": "Премести ову страницу", "tooltip-ca-watch": "Додајте ову страницу на свој списак надгледања", "tooltip-ca-unwatch": "Уклоните ову страницу са списка надгледања", @@ -2740,7 +2740,7 @@ "tooltip-t-upload": "Отпремите датотеке", "tooltip-t-specialpages": "Списак свих посебних страница", "tooltip-t-print": "Верзија ове странице за штампање", - "tooltip-t-permalink": "Трајна веза ка овој ревизији странице", + "tooltip-t-permalink": "Трајни линк ка овој ревизији странице", "tooltip-ca-nstab-main": "Погледајте страницу са садржајем", "tooltip-ca-nstab-user": "Погледајте корисничку страницу", "tooltip-ca-nstab-media": "Погледајте медијску страницу", @@ -2754,13 +2754,13 @@ "tooltip-minoredit": "Означите ову измену као мању", "tooltip-save": "Сачувајте своје промене", "tooltip-publish": "Објавите своје измене", - "tooltip-preview": "Прегледајте своје измене. Користите ово дугме пре чувања.", + "tooltip-preview": "Прегледајте своје промене. Користите ово дугме пре чувања.", "tooltip-diff": "Погледајте које промене сте направили на тексту", "tooltip-compareselectedversions": "Погледаjте разлике између две изабране ревизије ове странице", "tooltip-watch": "Додајте ову страницу на свој списак надгледања", "tooltip-watchlistedit-normal-submit": "Уклоните наслове", "tooltip-watchlistedit-raw-submit": "Ажурирај списак", - "tooltip-recreate": "Поново направите страницу иако је обрисана", + "tooltip-recreate": "Поново направите страницу иако је већ избрисана", "tooltip-upload": "Започните отпремање", "tooltip-rollback": "„Врати“ враћа измене последњег доприносиоца ове странице једним кликом", "tooltip-undo": "„Поништи” враћа ову измену и отвара образац за уређивање у претпрегледном моду. Дозвољава додавање разлога у резимеу.", @@ -2791,12 +2791,12 @@ "creditspage": "Аутори странице", "nocredits": "Не постоје подаци о аутору ове странице.", "spamprotectiontitle": "Филтер за заштиту од непожељних порука", - "spamprotectiontext": "Филтера против нежељених порука је блокирао чување ове странице.\nОво је вероватно изазвано везом до спољашњег сајта који се налази на црном списку.", + "spamprotectiontext": "Филтера против нежељених порука је блокирао чување ове странице.\nОво је вероватно изазвано линком до спољашњег сајта који се налази на црном списку.", "spamprotectionmatch": "Следећи текст је активирао наш филтер за нежељене поруке: $1", "spambot_username": "Чишћење непожељних порука у Медијавикији", - "spam_reverting": "Враћам на последњу ревизију која не садржи везе до $1", - "spam_blanking": "Све измене садрже везе до $1. Чистим", - "spam_deleting": "Све ревизије садрже везе до $1. Бришем", + "spam_reverting": "Враћам на последњу ревизију која не садржи линкове до $1", + "spam_blanking": "Све ревизије садрже линкове до $1. Празним", + "spam_deleting": "Све ревизије садрже линкове до $1. Бришем", "simpleantispam-label": "Провера против нежељеног садржаја. \nНе попуњавајте ово!", "pageinfo-title": "Информације за „$1“", "pageinfo-not-current": "Нажалост, немогуће је навести ове инфомације за старије ревизије.", @@ -2854,7 +2854,7 @@ "markaspatrolledtext": "Означи страницу као патролирану", "markaspatrolledtext-file": "Означи ову верзију датотеке као патролирану", "markedaspatrolled": "Означено као патролирано", - "markedaspatrolledtext": "Изабрана ревизија странице [[:$1]] је означена као патролирана.", + "markedaspatrolledtext": "Изабрана ревизија странице [[:$1]] означена је као патролирана.", "rcpatroldisabled": "Патролирање скорашњих измена је онемогућено", "rcpatroldisabledtext": "Могућност патролирања скорашњих измена је актуелно онемогућена.", "markedaspatrollederror": "Не могу да означим као патролирано.", @@ -2866,10 +2866,10 @@ "patrol-log-header": "Ово је евиденција патролираних ревизија.", "confirm-markpatrolled-button": "У реду", "confirm-markpatrolled-top": "Означити ревизију $3 странице $2 као патролирану?", - "deletedrevision": "Обрисана стара ревизија $1.", + "deletedrevision": "Избрисана стара ревизија $1.", "filedeleteerror-short": "Грешка при брисању датотеке: $1", "filedeleteerror-long": "Дошло је до грешака при брисању датотеке:\n\n$1", - "filedelete-missing": "Датотека „$1“ се не може обрисати јер не постоји.", + "filedelete-missing": "Не могу да избришем датотеку „$1“ јер не постоји.", "filedelete-old-unregistered": "Наведена ревизија датотеке „$1“ не постоји у бази података.", "filedelete-current-unregistered": "Наведена датотека „$1“ не постоји у бази података.", "filedelete-archive-read-only": "Сервер не може да пише по складишној фасцикли ($1).", @@ -2886,7 +2886,7 @@ "file-nohires": "Већа резолуција није доступна.", "svg-long-desc": "SVG датотека, номинално $1 × $2 пиксела, величина: $3", "svg-long-desc-animated": "Анимирана SVG датотека, номинално: $1 × $2 пиксела, величина: $3", - "svg-long-error": "Неисправна SVG датотека: $1", + "svg-long-error": "Неважећа SVG датотека: $1", "show-big-image": "Првобитна датотека", "show-big-image-preview": "Величина овог приказа: $1.", "show-big-image-preview-differ": "Величина $3 прегледа за ову $2 датотеку је $1.", @@ -2906,7 +2906,7 @@ "newimages-label": "Назив датотеке (или њен део):", "newimages-user": "IP адреса или корисничко име", "newimages-newbies": "Прикажи само доприносе нових налога", - "newimages-showbots": "Прикажи датотеке које су послали ботови", + "newimages-showbots": "Прикажи отпремања ботова", "newimages-hidepatrolled": "Сакриј патролирана отпремања", "newimages-mediatype": "Тип датотеке:", "noimages": "Нема ништа.", @@ -2939,7 +2939,7 @@ "saturday-at": "у суботу у $1", "sunday-at": "у недељу у $1", "yesterday-at": "Јуче у $1", - "bad_image_list": "Формат је следећи:\n\nРазматрају се само набрајања (редови који почињу са звездицом).\nПрва веза у реду мора да буде веза до неисправне датотеке.\nСве даљње везе у истом реду сматрају се изузецима.", + "bad_image_list": "Формат је следећи:\n\nРазматрају се само набрајања (редови који почињу са звездицом).\nПрви линк у реду мора да буде линк до неисправне датотеке.\nСви даљњи линкови у истом реду сматрају се изузецима.", "variantname-zh-hans": "hans", "variantname-zh-hant": "hant", "variantname-zh-cn": "cn", @@ -3146,7 +3146,7 @@ "exif-originaldocumentid": "Јединствени ID изворног документа", "exif-licenseurl": "Адреса лиценце за ауторска права", "exif-morepermissionsurl": "Резервни подаци о лиценцирању", - "exif-attributionurl": "При поновном коришћењу овог рада, користите везу до", + "exif-attributionurl": "При поновном коришћењу овог рада, користите линк до", "exif-preferredattributionname": "При поновном коришћењу овог рада, поставите заслуге", "exif-pngfilecomment": "Коментар на датотеку PNG", "exif-disclaimer": "Одрицање одговорности", @@ -3352,22 +3352,22 @@ "exif-urgency-other": "Прилагођени приоритет ($1)", "namespacesall": "сви", "monthsall": "све", - "confirmemail": "Потврда имејл адресе", - "confirmemail_noemail": "Нисте унели валидну имејл адресу у [[Special:Preferences|подешавањима]].", - "confirmemail_text": "{{SITENAME}} захтева да потврдите имејл адресу пре него што почнете да користите могућности имејла.\nКликните на дугме испод за слање поруке на вашу адресу.\nУ поруци ће се налазити веза с потврдним кодом;\nунесите је у прегледач да бисте потврдили да је ваша имејл адреса важећа.", - "confirmemail_pending": "Потврдни код вам је већ послат. Ако сте недавно отворили налог, онда вероватно треба да сачекате неколико минута да пристигне, пре него што поново затражите нови код.", - "confirmemail_send": "Пошаљи потврдни код", + "confirmemail": "Потврда имејл-адресе", + "confirmemail_noemail": "Нисте поставили важећу имејл-адресу у [[Special:Preferences|корисничким подешавањима]].", + "confirmemail_text": "{{SITENAME}} захтева да потврдите имејл адресу пре него што почнете да користите могућности имејла.\nКликните на дугме испод за слање поруке на вашу адресу.\nУ поруци ће се налазити линк са потврдним кодом;\nунесите је у прегледач да бисте потврдили да је ваша имејл адреса важећа.", + "confirmemail_pending": "Код за потврду вам је већ послат имејлом.\nАко сте недавно отворили налог, можда треба да сачекате неколико минута да пристигне пре него што поново затражите нови код.", + "confirmemail_send": "Пошаљи код за потврду", "confirmemail_sent": "Потврдна порука је послата.", - "confirmemail_oncreate": "Послат је потврдни код на вашу имејл адресу.\nОвај код није потребан за пријављивање, али вам треба да бисте укључили могућности имејла на викију.", + "confirmemail_oncreate": "Послат је код за потврду на вашу имејл адресу.\nОвај код није потребан за пријављивање, али вам треба да бисте укључили могућности имејла на викију.", "confirmemail_sendfailed": "{{SITENAME}} не може да пошаље имејл потврду.\nПроверите да ли је имејл адреса правилно написана.\n\nГрешка: $1", - "confirmemail_invalid": "Потврдни код је неисправан. Вероватно је истекао.", - "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": "Неважећи код за потврду.\nКод је можда истекао.", + "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}} је промењена", @@ -3377,7 +3377,7 @@ "scarytranscludefailed": "[Добављање шаблона за $1 није успело]", "scarytranscludefailed-httpstatus": "[Не могу да преузмем шаблон $1: HTTP $2]", "scarytranscludetoolong": "[URL адреса је предугачка]", - "deletedwhileediting": "Упозорење: Ова страница је обрисана након што сте почели са уређивањем!", + "deletedwhileediting": "Упозорење: Ова страница је избрисана након што сте почели са уређивањем!", "confirmrecreate": "{{GENDER:$1|Корисник|Корисница}} [[User:$1|$1]] ([[User talk:$1|разговор]]) је {{GENDER:$1|обрисао|обрисала}} ову страницу након што сте почели да је уређујете из следећег разлога:\n: $2\nПотврдите да стварно желите да направите страницу.", "confirmrecreate-noreason": "{{GENDER:$1|Корисник|Корисница}} [[User:$1|$1]] ([[User talk:$1|разговор]]) је {{GENDER:$1|обрисао|обрисала}} ову страницу након што сте почели да је уређујете. Потврдите да стварно желите да поново направите ову страницу.", "recreate": "Поново направи", @@ -3406,7 +3406,7 @@ "imgmultigo": "Иди!", "imgmultigoto": "Иди на страницу $1", "img-lang-default": "(подразумевани језик)", - "img-lang-info": "Прикажи ову слику на $1. $2", + "img-lang-info": "Рендеруј ову слику у $1. $2", "img-lang-go": "Иди", "ascending_abbrev": "раст.", "descending_abbrev": "опад.", @@ -3454,7 +3454,7 @@ "watchlistedit-too-many": "Има превише страница за приказ овде.", "watchlisttools-clear": "очисти списак надгледања", "watchlisttools-view": "погледај релевантне промене", - "watchlisttools-edit": "прикажи и уреди списак надгледања", + "watchlisttools-edit": "погледај и уреди списак надгледања", "watchlisttools-raw": "уреди сиров списак надгледања", "iranian-calendar-m1": "Фарвардин", "iranian-calendar-m2": "Ордибехешт", @@ -3598,7 +3598,7 @@ "specialpages-group-developer": "Програмерске алатке", "blankpage": "Празна страница", "intentionallyblankpage": "Ова страница је намерно остављена празном.", - "external_image_whitelist": " #Оставите овај ред онаквим какав јесте
\n#Испод додајте одломке регуларних израза (само део који се налази између //)\n#Они ће бити упоређени с адресама спољашњих слика\n#Оне које се поклапају биће приказане као слике, а преостале као везе до слика\n#Редови који почињу с тарабом се сматрају коментарима\n#Сви уноси су осетљиви на мала и велика слова\n\n#Додајте све одломке регуларних израза изнад овог реда. Овај ред не дирајте
", + "external_image_whitelist": " #Оставите овај ред онаквим какав јесте
\n#Испод додајте одломке регуларних израза (само део који се налази између //)\n#Они ће бити упоређени с адресама спољашњих слика\n#Оне које се поклапају биће приказане као слике, а преостале као линкови до слика\n#Редови који почињу с тарабом се сматрају коментарима\n#Сви уноси су осетљиви на мала и велика слова\n\n#Додајте све одломке регуларних израза изнад овог реда. Овај ред не дирајте
", "tags": "Важеће ознаке промена", "tag-filter": "Филтер [[Special:Tags|ознака]]:", "tag-filter-submit": "Филтрирај", @@ -3634,7 +3634,7 @@ "tags-source-manual": "Ручно је додају корисници и ботови", "tags-source-none": "Ван употребе", "tags-edit": "уреди", - "tags-delete": "обриши", + "tags-delete": "избриши", "tags-activate": "активирај", "tags-deactivate": "деактивирај", "tags-hitcount": "$1 {{PLURAL:$1|промена|промене|промена}}", @@ -3650,11 +3650,11 @@ "tags-create-warnings-below": "Правите нову ознаку, желите ли да наставите?", "tags-delete-title": "Брисање ознака", "tags-delete-explanation-initial": "Бришете ознаку „$1“ из базе података.", - "tags-delete-explanation-warning": "Ова радња је неповратна и не може се поништити, чак ни администратори базе података је не могу поништити. Будите сигурни да је ово ознака коју желите обрисати.", + "tags-delete-explanation-warning": "Ова радња је неповратна и не може да се поништи. Ово не могу да ураде чак ни администратори базе података. Будите сигурни да је ово ознака коју желите избрисати.", "tags-delete-reason": "Разлог:", - "tags-delete-submit": "Неповратно обриши ову ознаку", + "tags-delete-submit": "Неповратно избриши ову ознаку", "tags-delete-not-found": "Ознака „$1“ не постоји.", - "tags-delete-too-many-uses": "Ознака „$1” је примењена на више од $2 {{PLURAL:$2|ревизије|ревизија}}, што значи да се не може обрисати.", + "tags-delete-too-many-uses": "Ознака „$1” је примењена на више од $2 {{PLURAL:$2|ревизије|ревизија}}, што значи да се не може избрисати.", "tags-delete-no-permission": "Немате дозволу да бришете ознаке промена.", "tags-activate-title": "Активирање ознака", "tags-activate-question": "Активирате ознаку „$1“.", @@ -3695,14 +3695,14 @@ "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": "Трајна веза", + "permanentlink": "Трајни линк", "permanentlink-revid": "ID ревизије", "permanentlink-submit": "Иди на измену", "dberr-problems": "Дошло је до техничких проблема.", @@ -3785,7 +3785,7 @@ "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", @@ -3796,10 +3796,10 @@ "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": "На овој страници се налази списак измена у вези [[Special:Tags|ознака]]. Евиденција садржи само радње које су ручно извршили администратори; уноси за ознаке које је направио или обрисао вики софтвера се не налазе у овој евиденцији.", + "log-description-managetags": "На овој страници се налази списак измена у вези [[Special:Tags|ознака]]. Евиденција садржи само радње које су ручно извршили администратори; уноси за ознаке које је направио или избрисао вики софтвера се не налазе у овој евиденцији.", "logentry-managetags-create": "$1 је {{GENDER:$2|направио|направила}} ознаку „$4“", "logentry-managetags-delete": "$1 је {{GENDER:$2|обрисао|обрисала}} ознаку „$4“ (уклоњена је из $5 {{PLURAL:$5|ревизије или уноса у евиденцији|ревизија и/или уноса у евиденцији}})", "logentry-managetags-activate": "$1 је {{GENDER:$2|активирао|активирала}} ознаку „$4“ за употребу од стране корисника и ботова", @@ -3828,8 +3828,8 @@ "feedback-thanks-title": "Хвала вам!", "feedback-useragent": "Кориснички агент:", "searchsuggest-search": "Претрага", - "searchsuggest-containing": "садржи...", - "api-error-badtoken": "Унутрашња грешка: неисправан токен.", + "searchsuggest-containing": "садржи…", + "api-error-badtoken": "Унутрашња грешка: лош токен.", "api-error-emptypage": "Стварање нових празних страница није дозвољено.", "api-error-publishfailed": "Унутрашња грешка: сервер није успео да објави привремену датотеку.", "api-error-stashfailed": "Унутрашња грешка: сервер не може да сачува привремену датотеку.", @@ -3892,7 +3892,7 @@ "default-skin-not-found-row-enabled": "* $1 / $2 (омогућена)", "default-skin-not-found-row-disabled": "* $1 / $2 (онемогућена)", "mediastatistics": "Статистика медија", - "mediastatistics-summary": "Статистике о типовима послатих датотека. Овде су урачунате само најскорије верзије датотека. Старе или обрисане верзије нису урачунате.", + "mediastatistics-summary": "Статистике о типовима отпремљених датотека. Овде су урачунате само најскорије верзије датотека. Старе или избрисане верзије нису урачунате.", "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).", @@ -3905,6 +3905,7 @@ "mediastatistics-header-drawing": "Цртежи (векторске слике)", "mediastatistics-header-audio": "Аудио", "mediastatistics-header-video": "Видео", + "mediastatistics-header-multimedia": "Обогаћени медији", "mediastatistics-header-office": "Канцеларија", "mediastatistics-header-text": "Текстуалне", "mediastatistics-header-executable": "Извршне", @@ -3920,7 +3921,7 @@ "json-error-recursion": "Једна или више рекурзивних референци у вредности коју треба енкодирати.", "json-error-inf-or-nan": "Једна или више NAN или INF вредности у вредности коју треба енкодирати", "json-error-unsupported-type": "Дата је вредност типа која се не може енкодирати", - "headline-anchor-title": "Веза до овог одељка", + "headline-anchor-title": "Линк до овог одељка", "special-characters-group-latin": "Латиница", "special-characters-group-latinextended": "Проширена латиница", "special-characters-group-ipa": "МФА", @@ -3952,7 +3953,7 @@ "mw-widgets-mediasearch-noresults": "Нема резултата.", "mw-widgets-titleinput-description-new-page": "страница још увек не постоји", "mw-widgets-titleinput-description-redirect": "преусмерава на $1", - "mw-widgets-categoryselector-add-category-placeholder": "Додај категорију...", + "mw-widgets-categoryselector-add-category-placeholder": "Додајте категорију…", "mw-widgets-usersmultiselect-placeholder": "Додај још...", "date-range-from": "Од датума:", "date-range-to": "До датума:", @@ -4026,7 +4027,7 @@ "authmanager-authplugin-setpass-failed-message": "Додатак за потврду идентитета је одбио промену лозинке.", "authmanager-authplugin-create-fail": "Додатак за потврду идентитета је одбио прављење налога.", "authmanager-authplugin-setpass-denied": "Додатак за потврду идентитета не дозвољава мењање лозику.", - "authmanager-authplugin-setpass-bad-domain": "Неисправан домен.", + "authmanager-authplugin-setpass-bad-domain": "Неважећи домен.", "authmanager-autocreate-noperm": "Аутоматско прављење налога није дозвољено.", "authmanager-userdoesnotexist": "Кориснички налог „$1“ није отворен.", "authmanager-username-help": "Корисничко име за потврду идентитета.", @@ -4054,10 +4055,10 @@ "authform-wrongtoken": "Погрешан токен", "specialpage-securitylevel-not-allowed-title": "Није дозвољено", "specialpage-securitylevel-not-allowed": "Жао нам је, није вам дозвољено да користите ову страницу јер не могу да потврдим ваш идентитет.", - "authpage-cannot-login": "Не могу започети пријаву.", + "authpage-cannot-login": "Не могу да започнем пријаву.", "authpage-cannot-login-continue": "Не могу да наставим са пријавом. Ваша сесија је највероватније истекла.", - "authpage-cannot-create": "Не могу започети стварање налога.", - "authpage-cannot-link": "Не могу започети спајање налога.", + "authpage-cannot-create": "Не могу да започнем отварање налога.", + "authpage-cannot-link": "Не могу да започнем повезивање налога.", "cannotauth-not-allowed-title": "Приступ је одбијен", "cannotauth-not-allowed": "Није вам дозвољено да користите ову страницу", "changecredentials": "Промена акредитива", @@ -4087,13 +4088,13 @@ "pageid": "ID странице: $1", "rawhtml-notallowed": "<html> тагови не могу да се користе ван нормалних страница.", "gotointerwiki": "Напуштање пројекта {{SITENAME}}", - "gotointerwiki-invalid": "Одабрани наслов је невалидан.", + "gotointerwiki-invalid": "Наведени наслов је невалидан.", "gotointerwiki-external": "Управо ћете да напустите пројекат {{SITENAME}} да бисте на засебном веб-сајту посетили [[$2]].\n\n'''[$1 Продужи на $1]'''", "undelete-cantedit": "Не можете повратити ову страницу јер немате дозволу да је уређујете.", "undelete-cantcreate": "Не можете повратити ову страницу јер нема постојеће странице са овим именом и немате дозволу да направите ову страницу.", "pagedata-title": "Подаци странице", "pagedata-not-acceptable": "Није пронађен одговарајући облик. Подржане MIME-врсте: $1", - "pagedata-bad-title": "Неважећи наслов: $1.", + "pagedata-bad-title": "Невалидан наслов: $1.", "passwordpolicies": "Правила за лозинке", "passwordpolicies-group": "Група", "passwordpolicies-policies": "Правила", diff --git a/languages/i18n/zh-hant.json b/languages/i18n/zh-hant.json index 9de16b3829..a55372c15b 100644 --- a/languages/i18n/zh-hant.json +++ b/languages/i18n/zh-hant.json @@ -2112,7 +2112,7 @@ "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": "顯示額外日誌:", diff --git a/maintenance/addChangeTag.php b/maintenance/addChangeTag.php new file mode 100644 index 0000000000..b63a2e2b82 --- /dev/null +++ b/maintenance/addChangeTag.php @@ -0,0 +1,63 @@ +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; diff --git a/maintenance/benchmarks/Benchmarker.php b/maintenance/benchmarks/Benchmarker.php index 9bfebced5d..1559ee959d 100644 --- a/maintenance/benchmarks/Benchmarker.php +++ b/maintenance/benchmarks/Benchmarker.php @@ -94,6 +94,10 @@ abstract class Benchmarker extends Maintenance { // 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; diff --git a/maintenance/benchmarks/benchmarkTitleValue.php b/maintenance/benchmarks/benchmarkTitleValue.php index c60f4bbe82..6bd7953971 100644 --- a/maintenance/benchmarks/benchmarkTitleValue.php +++ b/maintenance/benchmarks/benchmarkTitleValue.php @@ -83,14 +83,22 @@ class BenchmarkTitleValue extends Benchmarker { [ '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' ], + ], ] ); } diff --git a/maintenance/categoryChangesAsRdf.php b/maintenance/categoryChangesAsRdf.php index d61335212c..564f7ced1b 100644 --- a/maintenance/categoryChangesAsRdf.php +++ b/maintenance/categoryChangesAsRdf.php @@ -157,8 +157,12 @@ SPARQLDI; $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 ) ); @@ -198,9 +202,10 @@ SPARQLDI; } /** - * 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 ) { @@ -356,16 +361,17 @@ SPARQL; } /** - * 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; @@ -540,14 +546,21 @@ SPARQL; } /** + * 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; @@ -558,6 +571,121 @@ SPARQL; $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" ) ); } } diff --git a/maintenance/language/zhtable/simpphrases_exclude.manual b/maintenance/language/zhtable/simpphrases_exclude.manual index b47d3b796e..cb7c7f4e02 100644 --- a/maintenance/language/zhtable/simpphrases_exclude.manual +++ b/maintenance/language/zhtable/simpphrases_exclude.manual @@ -30,4 +30,4 @@ 余 么 麽 - +拚 diff --git a/maintenance/language/zhtable/toCN.manual b/maintenance/language/zhtable/toCN.manual index 2453123533..0c6dbfd671 100644 --- a/maintenance/language/zhtable/toCN.manual +++ b/maintenance/language/zhtable/toCN.manual @@ -2498,6 +2498,7 @@ 密执安 密歇根 密西根 密歇根 紐澤西 新泽西 +奧勒岡 俄勒冈 蒙特婁 蒙特利尔 千里達及托巴哥 特立尼达和多巴哥 千里達 特立尼达 @@ -2522,7 +2523,6 @@ 主機板 主板 網際網絡 互联网 原始碼 源代码 -螢幕 屏幕 螢屏 荧屏 解像度 分辨率 IP位址 IP地址 @@ -2680,6 +2680,8 @@ A型肝炎 甲型肝炎 希拉莉 希拉里 文翠珊 特蕾莎·梅 德蕾莎·梅伊 特蕾莎·梅 +馬拉度納 马拉多纳 +馬勒當拿 马拉多纳 麻薩諸塞 马萨诸塞 東南亞國家協會 东南亚国家联盟 獨立國協 独联体 @@ -2691,3 +2693,6 @@ A型肝炎 甲型肝炎 烏龍麵 乌冬面 披索 比索 真人騷 真人秀 +帕運會 残奥会 +帕拉林匹克 残疾人奥林匹克 +傷殘奧林匹克 残疾人奥林匹克 diff --git a/maintenance/language/zhtable/toHK.manual b/maintenance/language/zhtable/toHK.manual index e85a51205f..c73c97ae2b 100644 --- a/maintenance/language/zhtable/toHK.manual +++ b/maintenance/language/zhtable/toHK.manual @@ -2854,6 +2854,7 @@ 愛荷華 艾奧瓦 爱荷华 艾奧瓦 得克萨斯 德克薩斯 +奧勒岡 俄勒岡 蒙特婁 蒙特利爾 紐賓士域 紐賓士域 加泰隆尼亞 加泰羅尼亞 @@ -2914,6 +2915,7 @@ C型肝炎 丙型肝炎 链接 連結 分辨率 解像度 解析度 解像度 +服务器 伺服器 智慧卡 智能卡 晶元 晶片 芯片 晶片 @@ -2921,7 +2923,6 @@ C型肝炎 丙型肝炎 晶体管 電晶體 源代码 原始碼 IP地址 IP位址 -屏幕 螢幕 荧屏 螢屏 版权信息 版權資訊 信息时代 資訊時代 @@ -3036,6 +3037,8 @@ IP地址 IP位址 翁山蘇姬 昂山素姬 德蕾莎·梅伊 文翠珊 特蕾莎·梅 文翠珊 +马拉多纳 馬勒當拿 +馬拉度納 馬勒當拿 西洋棋 國際象棋 隐私 私隱 隱私 私隱 @@ -3055,3 +3058,6 @@ IP地址 IP位址 塑料袋 膠袋 烏龍麵 烏冬麵 真人秀 真人騷 +帕運會 殘奧會 +帕拉林匹克 傷殘奧林匹克 +残疾人奥林匹克 傷殘奧林匹克 diff --git a/maintenance/language/zhtable/toSimp.manual b/maintenance/language/zhtable/toSimp.manual index 2a7f0acbe6..1abbf45407 100644 --- a/maintenance/language/zhtable/toSimp.manual +++ b/maintenance/language/zhtable/toSimp.manual @@ -70,6 +70,7 @@ 曾运乾 曾运乾 乾贵士 乾贵士 乾东 乾东 +乾顺 乾顺 柳诒徵 柳诒徵 於夫罗 於夫罗 於梨华 於梨华 @@ -278,3 +279,14 @@ 觔斗 斤斗 穀阳 穀阳 伊東豊雄 伊东丰雄 +峯會 峰会 +頂峯 顶峰 +巔峯 巅峰 +拚弃 拚弃 +拚棄 拚弃 +拚却 拚却 +拚卻 拚却 +满拚自尽 满拚自尽 +滿拚自盡 满拚自尽 +拚生尽死 拚生尽死 +拚生盡死 拚生尽死 diff --git a/maintenance/language/zhtable/toTW.manual b/maintenance/language/zhtable/toTW.manual index 1798437bea..91fe87a476 100644 --- a/maintenance/language/zhtable/toTW.manual +++ b/maintenance/language/zhtable/toTW.manual @@ -639,7 +639,6 @@ 晶体管 電晶體 IP地址 IP位址 解像度 解析度 -屏幕 螢幕 荧屏 螢屏 版权信息 版權資訊 航天器 太空飛行器 @@ -786,6 +785,8 @@ IP地址 IP位址 格莱美奖 葛萊美獎 乔布斯 賈伯斯 波里活 寶萊塢 +马拉多纳 馬拉度納 +馬勒當拿 馬拉度納 库尔德族 庫德族 库尔德人 庫德人 行人路 人行道 @@ -795,3 +796,7 @@ IP地址 IP位址 触摸屏 觸控螢幕 乌冬面 烏龍麵 真人騷 真人秀 +残奥会 帕運會 +殘奧會 帕運會 +残疾人奥林匹克 帕拉林匹克 +傷殘奧林匹克 帕拉林匹克 diff --git a/maintenance/language/zhtable/toTrad.manual b/maintenance/language/zhtable/toTrad.manual index c2fcb16239..6a30724fac 100644 --- a/maintenance/language/zhtable/toTrad.manual +++ b/maintenance/language/zhtable/toTrad.manual @@ -3,6 +3,9 @@ ‘ 『 ’ 』 ’s ’s +’m ’m +’t ’t +’re ’re 手塚治虫 手塚治虫 寇仇 寇讎 往日无仇 往日無讎 @@ -171,6 +174,7 @@ 簡筑翎 簡筑翎 楊雅筑 楊雅筑 尸羅精舍 尸羅精舍 +拚舍 拚捨 騰格里 騰格里 進制 進制 強制 強制 diff --git a/maintenance/language/zhtable/trad2simp.manual b/maintenance/language/zhtable/trad2simp.manual index 9a57047b42..3be11d49aa 100644 --- a/maintenance/language/zhtable/trad2simp.manual +++ b/maintenance/language/zhtable/trad2simp.manual @@ -130,7 +130,6 @@ U+05557啗|U+05556啖| 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嗥| diff --git a/maintenance/language/zhtable/tradphrases.manual b/maintenance/language/zhtable/tradphrases.manual index d153930a7c..e624e4093d 100644 --- a/maintenance/language/zhtable/tradphrases.manual +++ b/maintenance/language/zhtable/tradphrases.manual @@ -499,6 +499,7 @@ 出乖弄醜 出乖露醜 獲匪其醜 +長得醜 乙丑 丁丑 己丑 @@ -561,7 +562,6 @@ 括髮 髡髮 鵠髮 -截髮 解髮佯狂 淨髮 噙齒戴髮 @@ -1125,7 +1125,6 @@ 犖确 磽确 确瘠 -拚捨 廣捨 齊王捨牛 捨墮 @@ -1400,6 +1399,7 @@ 這麼幹 幹這 幹仗 +包幹 李連杰 周杰 杰倫 @@ -1931,7 +1931,7 @@ 挌鬥 好鬥 鬥合 -拚鬥 +拼鬥 兩虎共鬥 兩鼠鬥穴 鬥犀臺 @@ -2390,6 +2390,7 @@ 穩健的台風 台風獎 電視台風 +舞台風格 足球台 網球台 合府上 @@ -2610,11 +2611,6 @@ 溫洛克期 科尼亞克期 馬斯垂克期 -滿拚自盡 -拚生盡死 -拚卻 -拚老命 -拚絕 成於思 單單於 名單於 @@ -2776,7 +2772,8 @@ 繫鞋帶 繫船 繫着 -重回 +重回 #分詞 +收回 挑大樑 扛大樑 后豐 @@ -3299,6 +3296,11 @@ 帝后臺 紅后假說 尊后 +后姓 +電影後 +封為后 +天神之后 +夏后氏 前往 新井里美 樗里子 @@ -3389,7 +3391,6 @@ 湧水 高涌泉 涌水塘 -后姓 計劃 党姓 党家 @@ -3440,11 +3441,8 @@ 煙臺 太醜 御製 -電影後 -封為后 皮托管 白面包青天 -天神之后 你誇 誇你 誇我 @@ -3475,6 +3473,7 @@ 自誇 誇稱 誇讚 +像讚 黎克特制 筆桿 袋桿 diff --git a/maintenance/language/zhtable/tradphrases_exclude.manual b/maintenance/language/zhtable/tradphrases_exclude.manual index eaea6805ca..ac9087c905 100644 --- a/maintenance/language/zhtable/tradphrases_exclude.manual +++ b/maintenance/language/zhtable/tradphrases_exclude.manual @@ -781,3 +781,4 @@ 髮姐 崙 鬆起 +拚 diff --git a/maintenance/resources/foreign-resources.yaml b/maintenance/resources/foreign-resources.yaml new file mode 100644 index 0000000000..b8d9848358 --- /dev/null +++ b/maintenance/resources/foreign-resources.yaml @@ -0,0 +1,50 @@ +### Format of this file +# +# The top-level keys are module names (as registered in Resources.php). +# The values of these keys are resource descriptors. +# +# In each resource descriptor object, the `src` and `integrity` keys are required. +# +# * `src`: Full URL to a remote resource. +# * `integrity`: Cryptographic hash used to verify the remote content. +# Uses the "integrity metadata" format defined at . +# * `dest`: An object mapping paths from the remote resource to a destination in +# `/resources/lib/$module/`. The value may be omitted to indicate that +# paths should be extracted to the destination directory itself. +oojs: + 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: + 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: diff --git a/maintenance/resources/manageForeignResources.php b/maintenance/resources/manageForeignResources.php new file mode 100644 index 0000000000..528d6e7c4c --- /dev/null +++ b/maintenance/resources/manageForeignResources.php @@ -0,0 +1,246 @@ +addDescription( <<addArg( 'module', 'Name of a single module (Default: all)', false ); + $this->addOption( 'update', ' resources/lib/ missing integrity metadata' ); + $this->addOption( 'make-sri', 'Compute missing integrity metadata' ); + $this->addOption( 'verbose', 'Be verbose' ); + + // 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; + $module = $this->getArg(); + $makeSRI = $this->hasOption( 'make-sri' ); + + $registry = $this->parseBasicYaml( + file_get_contents( __DIR__ . '/foreign-resources.yaml' ) + ); + foreach ( $registry as $moduleName => $info ) { + if ( $module !== null && $moduleName !== $module ) { + continue; + } + $this->verbose( "\n### {$moduleName}\n\n" ); + + // Validate required keys + $info += [ 'src' => null, 'integrity' => null, 'dest' => null ]; + if ( $info['src'] === null ) { + $this->fatalError( "Module '$moduleName' must have a 'src' key." ); + } + $integrity = is_string( $info['integrity'] ) ? $info['integrity'] : $makeSRI; + if ( $integrity === false ) { + $this->fatalError( "Module '$moduleName' must have an 'integrity' key." ); + } + + // Download the resource + $data = Http::get( $info['src'], [ 'followRedirects' => false ] ); + if ( $data === false ) { + $this->fatalError( "Failed to download resource for '$moduleName'." ); + } + + // Validate integrity metadata + $this->output( "... checking integrity of '{$moduleName}'\n" ); + $algo = $integrity === true ? $this->defaultAlgo : explode( '-', $integrity )[0]; + $actualIntegrity = $algo . '-' . base64_encode( hash( $algo, $data, true ) ); + if ( $integrity === true ) { + $this->output( "Integrity for '{$moduleName}':\n\t${actualIntegrity}\n" ); + continue; + } elseif ( $integrity !== $actualIntegrity ) { + $this->fatalError( "Integrity check failed for '{$moduleName}:\n" . + "Expected: {$integrity}\n" . + "Actual: {$actualIntegrity}" + ); + } + + // Determine destination + $destDir = "{$IP}/resources/lib/$moduleName"; + $this->output( "... extracting files for '{$moduleName}'\n" ); + $this->handleTypeTar( $moduleName, $data, $destDir, $info ); + } + + // Clean up + wfRecursiveRemoveDir( $this->tmpParentDir ); + $this->output( "\nDone!\n" ); + } + + private function handleTypeTar( $moduleName, $data, $destDir, array $info ) { + global $IP; + wfRecursiveRemoveDir( $this->tmpParentDir ); + if ( !wfMkdirParents( $this->tmpParentDir ) ) { + $this->fatalError( "Unable to create {$this->tmpParentDir}" ); + } + + // Write resource to temporary file and open it + $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 ) { + // Replace the entire directory as-is + if ( !$this->hasOption( 'update' ) ) { + $this->output( "[dry run] Would replace /resources/lib/$moduleName\n" ); + } else { + wfRecursiveRemoveDir( $destDir ); + if ( !rename( $tmpDir, $destDir ) ) { + $this->fatalError( "Could not move $destDir to $tmpDir." ); + } + } + return; + } + + // Create and/or empty the destination + if ( !$this->hasOption( 'update' ) ) { + $this->output( "... [dry run] would empty /resources/lib/$moduleName\n" ); + } else { + wfRecursiveRemoveDir( $destDir ); + wfMkdirParents( $destDir ); + } + + // Expand and normalise the 'dest' entries + $toCopy = []; + foreach ( $info['dest'] as $fromSubPath => $toSubPath ) { + // Use glob() to expand wildcards and check existence + $fromPaths = glob( "{$tmpDir}/{$fromSubPath}", GLOB_BRACE ); + if ( !$fromPaths ) { + $this->fatalError( "Path '$fromSubPath' of '$moduleName' not found." ); + } + foreach ( $fromPaths as $fromPath ) { + $toCopy[$fromPath] = $toSubPath === null + ? "$destDir/" . basename( $fromPath ) + : "$destDir/$toSubPath/" . basename( $fromPath ); + } + } + foreach ( $toCopy as $from => $to ) { + if ( !$this->hasOption( 'update' ) ) { + $shortFrom = strtr( $from, [ "$tmpDir/" => '' ] ); + $shortTo = strtr( $to, [ "$IP/" => '' ] ); + $this->output( "... [dry run] would move $shortFrom to $shortTo\n" ); + } else { + $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 ); + } + } + + /** + * 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; diff --git a/maintenance/resources/update-oojs.sh b/maintenance/resources/update-oojs.sh deleted file mode 100755 index fd7b860d1d..0000000000 --- a/maintenance/resources/update-oojs.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/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 []" - 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 <&2 "Usage: $0 []" - 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 < [ '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', @@ -221,14 +221,14 @@ return [ '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' ], @@ -248,7 +248,7 @@ return [ 'targets' => [ 'desktop', 'mobile' ], ], 'jquery.hoverIntent' => [ - 'scripts' => 'resources/lib/jquery/jquery.hoverIntent.js', + 'scripts' => 'resources/lib/jquery.hoverIntent.js', ], 'jquery.i18n' => [ 'scripts' => [ @@ -295,7 +295,7 @@ return [ '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', @@ -313,7 +313,7 @@ return [ ], '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' ], @@ -340,11 +340,11 @@ return [ '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 */ @@ -2868,9 +2868,9 @@ return [ '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', diff --git a/resources/lib/jquery.async.js b/resources/lib/jquery.async.js new file mode 100644 index 0000000000..2161f6b9e3 --- /dev/null +++ b/resources/lib/jquery.async.js @@ -0,0 +1,69 @@ +/* + * 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 diff --git a/resources/lib/jquery.ba-throttle-debounce.js b/resources/lib/jquery.ba-throttle-debounce.js new file mode 100644 index 0000000000..fa30bdfffe --- /dev/null +++ b/resources/lib/jquery.ba-throttle-debounce.js @@ -0,0 +1,252 @@ +/*! + * 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 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 + // 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 + // 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); diff --git a/resources/lib/jquery.cookie.js b/resources/lib/jquery.cookie.js new file mode 100644 index 0000000000..3fb201c6a0 --- /dev/null +++ b/resources/lib/jquery.cookie.js @@ -0,0 +1,90 @@ +/*! + * 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); diff --git a/resources/lib/jquery.form.js b/resources/lib/jquery.form.js new file mode 100644 index 0000000000..13e9a55c58 --- /dev/null +++ b/resources/lib/jquery.form.js @@ -0,0 +1,1089 @@ +/*! + * 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 = $("").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 = $('