Merge "Support LESS stylesheets in ResourceLoader"
authorBrion VIBBER <brion@wikimedia.org>
Mon, 23 Sep 2013 19:59:49 +0000 (19:59 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 23 Sep 2013 19:59:49 +0000 (19:59 +0000)
29 files changed:
includes/AutoLoader.php
includes/HtmlFormatter.php [new file with mode: 0644]
includes/installer/Installer.i18n.php
languages/Language.php
languages/messages/MessagesAf.php
languages/messages/MessagesAr.php
languages/messages/MessagesBr.php
languages/messages/MessagesDe.php
languages/messages/MessagesDiq.php
languages/messages/MessagesDv.php
languages/messages/MessagesEn.php
languages/messages/MessagesEs.php
languages/messages/MessagesFi.php
languages/messages/MessagesFo.php
languages/messages/MessagesFr.php
languages/messages/MessagesHu.php
languages/messages/MessagesKo.php
languages/messages/MessagesNds_nl.php
languages/messages/MessagesPdc.php
languages/messages/MessagesPms.php
languages/messages/MessagesPt_br.php
languages/messages/MessagesRu.php
languages/messages/MessagesSi.php
languages/messages/MessagesSk.php
languages/messages/MessagesSr_ec.php
languages/messages/MessagesSr_el.php
languages/messages/MessagesSv.php
languages/messages/MessagesUk.php
tests/phpunit/includes/HtmlFormatterTest.php [new file with mode: 0644]

index daba73f..54bffab 100644 (file)
@@ -111,6 +111,7 @@ $wgAutoloadLocalClasses = array(
        'HistoryBlobStub' => 'includes/HistoryBlob.php',
        'Hooks' => 'includes/Hooks.php',
        'Html' => 'includes/Html.php',
+       'HtmlFormatter' => 'includes/HtmlFormatter.php',
        'HTMLApiField' => 'includes/HTMLForm.php',
        'HTMLButtonField' => 'includes/HTMLForm.php',
        'HTMLCheckField' => 'includes/HTMLForm.php',
diff --git a/includes/HtmlFormatter.php b/includes/HtmlFormatter.php
new file mode 100644 (file)
index 0000000..248a76f
--- /dev/null
@@ -0,0 +1,356 @@
+<?php
+/**
+ * Performs transformations of HTML by wrapping around libxml2 and working
+ * around its countless bugs.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+class HtmlFormatter {
+       /**
+        * @var DOMDocument
+        */
+       private $doc;
+
+       private $html;
+       private $itemsToRemove = array();
+       private $elementsToFlatten = array();
+       protected $removeMedia = false;
+
+       /**
+        * Constructor
+        *
+        * @param string $html: Text to process
+        */
+       public function __construct( $html ) {
+               $this->html = $html;
+       }
+
+       /**
+        * Turns a chunk of HTML into a proper document
+        * @param string $html
+        * @return string
+        */
+       public static function wrapHTML( $html ) {
+               return '<!doctype html><html><head></head><body>' . $html . '</body></html>';
+       }
+
+       /**
+        * Override this in descendant class to modify HTML after it has been converted from DOM tree
+        * @param string $html: HTML to process
+        * @return string: Processed HTML
+        */
+       protected function onHtmlReady( $html ) {
+               return $html;
+       }
+
+       /**
+        * @return DOMDocument: DOM to manipulate
+        */
+       public function getDoc() {
+               if ( !$this->doc ) {
+                       $html = mb_convert_encoding( $this->html, 'HTML-ENTITIES', 'UTF-8' );
+
+                       // Workaround for bug that caused spaces before references
+                       // to disappear during processing:
+                       // https://bugzilla.wikimedia.org/show_bug.cgi?id=53086
+                       //
+                       // Please replace with a better fix if one can be found.
+                       $html = str_replace( ' <', '&#32;<', $html );
+
+                       libxml_use_internal_errors( true );
+                       $loader = libxml_disable_entity_loader();
+                       $this->doc = new DOMDocument();
+                       $this->doc->strictErrorChecking = false;
+                       $this->doc->loadHTML( $html );
+                       libxml_disable_entity_loader( $loader );
+                       libxml_use_internal_errors( false );
+                       $this->doc->encoding = 'UTF-8';
+               }
+               return $this->doc;
+       }
+
+       /**
+        * Sets whether images/videos/sounds should be removed from output
+        * @param bool $flag
+        */
+       public function setRemoveMedia( $flag = true ) {
+               $this->removeMedia = $flag;
+       }
+
+       /**
+        * Adds one or more selector of content to remove. A subset of CSS selector
+        * syntax is supported:
+        *
+        *   <tag>
+        *   <tag>.class
+        *   .<class>
+        *   #<id>
+        *
+        * @param Array|string $selectors: Selector(s) of stuff to remove
+        */
+       public function remove( $selectors ) {
+               $this->itemsToRemove = array_merge( $this->itemsToRemove, (array)$selectors );
+       }
+
+       /**
+        * Adds one or more element name to the list to flatten (remove tag, but not its content)
+        * Can accept undelimited regexes
+        *
+        * Note this interface may fail in surprising unexpected ways due to usage of regexes,
+        * so should not be relied on for HTML markup security measures.
+        *
+        * @param Array|string $elements: Name(s) of tag(s) to flatten
+        */
+       public function flatten( $elements ) {
+               $this->elementsToFlatten = array_merge( $this->elementsToFlatten, (array)$elements );
+       }
+
+       /**
+        * Instructs the formatter to flatten all tags
+        */
+       public function flattenAllTags() {
+               $this->flatten( '[?!]?[a-z0-9]+' );
+       }
+
+       /**
+        * Removes content we've chosen to remove
+        */
+       public function filterContent() {
+               wfProfileIn( __METHOD__ );
+               $removals = $this->parseItemsToRemove();
+
+               if ( !$removals ) {
+                       wfProfileOut( __METHOD__ );
+                       return;
+               }
+
+               $doc = $this->getDoc();
+
+               // Remove tags
+
+               // You can't remove DOMNodes from a DOMNodeList as you're iterating
+               // over them in a foreach loop. It will seemingly leave the internal
+               // iterator on the foreach out of wack and results will be quite
+               // strange. Though, making a queue of items to remove seems to work.
+               $domElemsToRemove = array();
+               foreach ( $removals['TAG'] as $tagToRemove ) {
+                       $tagToRemoveNodes = $doc->getElementsByTagName( $tagToRemove );
+                       foreach ( $tagToRemoveNodes as $tagToRemoveNode ) {
+                               if ( $tagToRemoveNode ) {
+                                       $domElemsToRemove[] = $tagToRemoveNode;
+                               }
+                       }
+               }
+
+               $this->removeElements( $domElemsToRemove );
+
+               // Elements with named IDs
+               $domElemsToRemove = array();
+               foreach ( $removals['ID'] as $itemToRemove ) {
+                       $itemToRemoveNode = $doc->getElementById( $itemToRemove );
+                       if ( $itemToRemoveNode ) {
+                               $domElemsToRemove[] = $itemToRemoveNode;
+                       }
+               }
+               $this->removeElements( $domElemsToRemove );
+
+               // CSS Classes
+               $domElemsToRemove = array();
+               $xpath = new DOMXpath( $doc );
+               foreach ( $removals['CLASS'] as $classToRemove ) {
+                       $elements = $xpath->query( '//*[contains(@class, "' . $classToRemove . '")]' );
+
+                       /** @var $element DOMElement */
+                       foreach ( $elements as $element ) {
+                               $classes = $element->getAttribute( 'class' );
+                               if ( preg_match( "/\b$classToRemove\b/", $classes ) && $element->parentNode ) {
+                                       $domElemsToRemove[] = $element;
+                               }
+                       }
+               }
+               $this->removeElements( $domElemsToRemove );
+
+               // Tags with CSS Classes
+               foreach ( $removals['TAG_CLASS'] as $classToRemove ) {
+                       $parts = explode( '.', $classToRemove );
+
+                       $elements = $xpath->query(
+                               '//' . $parts[0] . '[@class="' . $parts[1] . '"]'
+                       );
+
+                       $this->removeElements( $elements );
+               }
+
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * Removes a list of elelments from DOMDocument
+        * @param array|DOMNodeList $elements
+        */
+       private function removeElements( $elements ) {
+               $list = $elements;
+               if ( $elements instanceof DOMNodeList ) {
+                       $list = array();
+                       foreach ( $elements as $element ) {
+                               $list[] = $element;
+                       }
+               }
+               /** @var $element DOMElement */
+               foreach ( $list as $element ) {
+                       if ( $element->parentNode ) {
+                               $element->parentNode->removeChild( $element );
+                       }
+               }
+       }
+
+       /**
+        * libxml in its usual pointlessness converts many chars to entities - this function
+        * perfoms a reverse conversion
+        * @param string $html
+        * @return string
+        */
+       private function fixLibXML( $html ) {
+               wfProfileIn( __METHOD__ );
+               static $replacements;
+               if ( ! $replacements ) {
+                       // We don't include rules like '&#34;' => '&amp;quot;' because entities had already been
+                       // normalized by libxml. Using this function with input not sanitized by libxml is UNSAFE!
+                       $replacements = new ReplacementArray( array(
+                               '&quot;' => '&amp;quot;',
+                               '&amp;' => '&amp;amp;',
+                               '&lt;' => '&amp;lt;',
+                               '&gt;' => '&amp;gt;',
+                       ) );
+               }
+               $html = $replacements->replace( $html );
+               $html = mb_convert_encoding( $html, 'UTF-8', 'HTML-ENTITIES' );
+               wfProfileOut( __METHOD__ );
+               return $html;
+       }
+
+       /**
+        * Performs final transformations and returns resulting HTML
+        *
+        * @param DOMElement|string|null $element: ID of element to get HTML from or false to get it from the whole tree
+        * @return string: Processed HTML
+        */
+       public function getText( $element = null ) {
+               wfProfileIn( __METHOD__ );
+
+               if ( $this->doc ) {
+                       if ( $element !== null && !( $element instanceof DOMElement ) ) {
+                               $element = $this->doc->getElementById( $element );
+                       }
+                       if ( $element ) {
+                               $body = $this->doc->getElementsByTagName( 'body' )->item( 0 );
+                               $nodesArray = array();
+                               foreach ( $body->childNodes as $node ) {
+                                       $nodesArray[] = $node;
+                               }
+                               foreach ( $nodesArray as $nodeArray ) {
+                                       $body->removeChild( $nodeArray );
+                               }
+                               $body->appendChild( $element );
+                       }
+                       $html = $this->doc->saveHTML();
+                       $html = $this->fixLibXml( $html );
+               } else {
+                       $html = $this->html;
+               }
+               if ( wfIsWindows() ) {
+                       // Appears to be cleanup for CRLF misprocessing of unknown origin
+                       // when running server on Windows platform.
+                       //
+                       // If this error continues in the future, please track it down in the
+                       // XML code paths if possible and fix there.
+                       $html = str_replace( '&#13;', '', $html );
+               }
+               $html = preg_replace( '/<!--.*?-->|^.*?<body>|<\/body>.*$/s', '', $html );
+               $html = $this->onHtmlReady( $html );
+
+               if ( $this->elementsToFlatten ) {
+                       $elements = implode( '|', $this->elementsToFlatten );
+                       $html = preg_replace( "#</?($elements)\\b[^>]*>#is", '', $html );
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $html;
+       }
+
+       /**
+        * @param $selector: CSS selector to parse
+        * @param $type
+        * @param $rawName
+        * @return bool: Whether the selector was successfully recognised
+        */
+       protected function parseSelector( $selector, &$type, &$rawName ) {
+               if ( strpos( $selector, '.' ) === 0 ) {
+                       $type = 'CLASS';
+                       $rawName = substr( $selector, 1 );
+               } elseif ( strpos( $selector, '#' ) === 0 ) {
+                       $type = 'ID';
+                       $rawName = substr( $selector, 1 );
+               } elseif ( strpos( $selector, '.' ) !== 0 &&
+                       strpos( $selector, '.' ) !== false )
+               {
+                       $type = 'TAG_CLASS';
+                       $rawName = $selector;
+               } elseif ( strpos( $selector, '[' ) === false
+                       && strpos( $selector, ']' ) === false )
+               {
+                       $type = 'TAG';
+                       $rawName = $selector;
+               } else {
+                       throw new MWException( __METHOD__ . "(): unrecognized selector '$selector'" );
+               }
+
+               return true;
+       }
+
+       /**
+        * Transforms CSS selectors into an internal representation suitable for processing
+        * @return array
+        */
+       protected function parseItemsToRemove() {
+               wfProfileIn( __METHOD__ );
+               $removals = array(
+                       'ID' => array(),
+                       'TAG' => array(),
+                       'CLASS' => array(),
+                       'TAG_CLASS' => array(),
+               );
+
+               foreach ( $this->itemsToRemove as $itemToRemove ) {
+                       $type = '';
+                       $rawName = '';
+                       if ( $this->parseSelector( $itemToRemove, $type, $rawName ) ) {
+                               $removals[$type][] = $rawName;
+                       }
+               }
+
+               if ( $this->removeMedia ) {
+                       $removals['TAG'][] = 'img';
+                       $removals['TAG'][] = 'audio';
+                       $removals['TAG'][] = 'video';
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $removals;
+       }
+}
index e42c214..f77ff32 100644 (file)
@@ -12330,7 +12330,7 @@ Kuckt Är php.ini no a vergewëssert Iech datt <code>session.save_path</code>  o
        'config-restart' => 'Jo, neistarten',
        'config-welcome' => "=== Iwwerpréifung vum Installatiounsenvironnement ===
 Et gi grondsätzlech Iwwerpréifunge gemaach fir ze kucken ob den Environnment gëeegent ass fir MediaWiki z'installéieren.
-Dir sollt d'Resultater vun dëser Iwwerpréifung ugi wann Dir während der Installatioun Hëllef braucht.", # Fuzzy
+Dir sollt d'Resultater vun dëser Iwwerpréifung ugi wann Dir während der Installatioun Hëllef frot wéi Dir D'Installatioun ofschléisse kënnt.",
        'config-sidebar' => '* [//www.mediawiki.org MediaWiki Haaptsäit]
 * [//www.mediawiki.org/wiki/Help:Contents Benotzerguide]
 * [//www.mediawiki.org/wiki/Manual:Contents Guide fir Administrateuren]
index 356726f..20bef5d 100644 (file)
@@ -2288,6 +2288,8 @@ class Language {
                        // Timestamp within the past week: show the day of the week and time
                        $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
                        $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
+                       // Messages:
+                       // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
                        $ts = wfMessage( "$weekday-at" )
                                ->inLanguage( $this )
                                ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
index 0ddbc5f..d2ae272 100644 (file)
@@ -19,6 +19,7 @@
  * @author Purodha
  * @author Reedy
  * @author SPQRobin
+ * @author Servien
  * @author Shirayuki
  * @author Spacebirdy
  * @author Xethron
@@ -1573,7 +1574,7 @@ As u dit verskaf, sal dit gebruik word om erkenning vir u werk te gee.',
 'action-edit' => 'hierdie bladsy te wysig nie',
 'action-createpage' => 'skep bladsye',
 'action-createtalk' => 'skep besprekingsblaaie',
-'action-createaccount' => 'skep die genruiker',
+'action-createaccount' => 'skep die gebruiker',
 'action-minoredit' => "merk die wysiging as 'n klein verandering",
 'action-move' => 'skuif die bladsy',
 'action-move-subpages' => 'skuif die bladsy met sy subbladsye',
index 6f3281c..983e701 100644 (file)
@@ -2525,7 +2525,7 @@ $1',
 'linksearch-error' => 'الكروت الخاصة يمكن أن تظهر فقط في بداية اسم المضيف.',
 
 # Special:ListUsers
-'listusersfrom' => 'اعرض Ø§Ù\84Ù\85ستخدÙ\85Ù\8aÙ\86 Ø¨Ø¯Ø¡Ø§Ù\8b من:',
+'listusersfrom' => 'اعرض Ø§Ù\84Ù\85ستخدÙ\85Ù\8aÙ\86 Ø§Ø¨ØªØ¯Ø§Ø¡ من:',
 'listusers-submit' => 'اعرض',
 'listusers-noresult' => 'لم يتم إيجاد مستخدم.',
 'listusers-blocked' => '(ممنوع)',
index caad8d6..0ad0fed 100644 (file)
@@ -210,7 +210,7 @@ $messages = array(
 'tog-shownumberswatching' => 'Diskouez an niver a lennerien',
 'tog-oldsig' => 'Ar sinadur zo evit poent :',
 'tog-fancysig' => 'Ober gant ar sinadur evel pa vefe wikitestenn (hep liamm emgefre)',
-'tog-uselivepreview' => 'Implijout Rakwelet prim (JavaScript) (taol-arnod)',
+'tog-uselivepreview' => 'Implijout Rakwelet prim (taol-arnod)',
 'tog-forceeditsummary' => 'Kemenn din pa ne skrivan netra er stern diverrañ',
 'tog-watchlisthideown' => "Kuzhat ma c'hemmoù er rollad evezhiañ",
 'tog-watchlisthidebots' => 'Kuzhat kemmoù ar botoù er rollad evezhiañ',
@@ -2008,6 +2008,8 @@ Marteze a-walc'h e fell deoc'h kemmañ an deskrivadur anezhi war ar [$2 bajenn d
 'statistics-users-active-desc' => "Implijerien o deus degaset da nebeutañ ur c'hemm {{PLURAL:$1|an deiz paseet|e-kerzh an $1 deiz diwezhañ}}",
 'statistics-mostpopular' => 'Pajennoù muiañ sellet',
 
+'pageswithprop' => 'Pajennoù gant ur perzh pajenn',
+'pageswithprop-legend' => 'Pajennoù gant ur perzh pajenn',
 'pageswithprop-prop' => 'Anv ar perzh :',
 'pageswithprop-submit' => 'Mont',
 
@@ -3919,7 +3921,7 @@ Sañset oc'h bezañ resevet [{{SERVER}}{{SCRIPTPATH}}/COPYING un eilskrid eus ar
 'logentry-delete-delete' => 'Diverket eo bet ar bajenn $3 gant $1',
 'logentry-delete-restore' => 'Assavet eo bet ar bajenn $3 gant $1',
 'logentry-delete-event' => "Kemmet eo bet gwelusted {{PLURAL:$5|un darvoud eus ar marilh|$5 darvoud eus ar marilh}} d'an $3 gant $1 : $4",
-'logentry-delete-revision' => 'Kemmet eo bet gwelusted {{PLURAL:$5|reizhadenn}} war ar bajenn $3 gant $1 : $4',
+'logentry-delete-revision' => 'Kemmet eo bet gwelusted {{PLURAL:$5|$5reizhadenn}} war ar bajenn $3 gant $1 : $4',
 'logentry-delete-event-legacy' => 'Kemmet eo bet gwelusted darvoudoù ar marilh $3 gant $1',
 'logentry-delete-revision-legacy' => 'Kemmet eo bet gwelusted ar reizhadennoù war ar bajenn $3 gant $1',
 'logentry-suppress-delete' => '$1 {{GENDER:$2|en deus dilamet}} ar bajenn $3',
index 3b1f43c..3192912 100644 (file)
@@ -665,7 +665,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'Über {{SITENAME}}',
 'aboutpage' => 'Project:Über_{{SITENAME}}',
-'copyright' => 'Der Inhalt ist verfügbar unter der Lizenz $1.',
+'copyright' => 'Der Inhalt ist verfügbar unter der Lizenz $1, sofern nicht anders angegeben.',
 'copyrightpage' => '{{ns:project}}:Urheberrechte',
 'currentevents' => 'Aktuelle Ereignisse',
 'currentevents-url' => 'Project:Aktuelle Ereignisse',
index c6f696f..d963e0d 100644 (file)
@@ -568,7 +568,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'Heqa {{SITENAME}}i de',
 'aboutpage' => 'Project:Heqdê cı',
-'copyright' => 'Zerrek bınê $1 dero.',
+'copyright' => 'Zerrekacı $1 bındı not biya.',
 'copyrightpage' => '{{ns:project}}:Heqa telifi',
 'currentevents' => 'Veng u vac',
 'currentevents-url' => 'Project:Veng u vac',
@@ -657,6 +657,7 @@ Seba lista pelanê xasanê vêrdeyan reca kena: [[Special:SpecialPages|{{int:spe
 # General errors
 'error' => 'Xırab',
 'databaseerror' => 'Xeta serveri',
+'databaseerror-query' => 'Perskerdış:$1',
 'databaseerror-function' => 'Fonksiyon: $1',
 'databaseerror-error' => 'Xırab: $1',
 'laggedslavemode' => 'Diqet: Pel de newe vıraşteyi belka çini .',
@@ -888,6 +889,7 @@ Bıne vındere u newe ra dest pê bıkere.',
 # Special:PasswordReset
 'passwordreset' => 'Parola reset ke',
 'passwordreset-text-one' => 'Na form de parola reset kerdış temamiye',
+'passwordreset-text-many' => '{{PLURAL:$1|Qande parola reset kerdışi cayanra taynın pırkeri}}',
 'passwordreset-legend' => 'Parola reset ke',
 'passwordreset-disabled' => 'Parola reset kerdış ena viki sera qefılneyayo.',
 'passwordreset-username' => 'Nameyê karberi:',
@@ -4341,10 +4343,15 @@ Ena sita dı newke xırabiya teknik esta.',
 'rotate-comment' => 'Resim heta sehata $1 {{PLURAL:$1|derece|derecey}} bi cerx',
 
 # Limit report
+'limitreport-title' => 'Agoznaye malumata profili:',
+'limitreport-cputime' => 'CPU dem karnayış',
 'limitreport-cputime-value' => '$1 {{PLURAL:$1|saniye|saniyeyan}}',
+'limitreport-walltime' => 'Raştay demdı bıkarn',
 'limitreport-walltime-value' => '$1 {{PLURAL:$1|saniye|saniyeyan}}',
 'limitreport-postexpandincludesize-value' => '$1/$2 bayt',
 'limitreport-templateargumentsize' => 'Ebata hacetandi şablonan',
 'limitreport-templateargumentsize-value' => '$1/$2 bayt',
+'limitreport-expansiondepth' => 'Tewr veşi herayina dergbiyayışi',
+'limitreport-expensivefunctioncount' => 'Amoriya fonksiyonde vay agozni',
 
 );
index baec32d..04b7ff0 100644 (file)
@@ -261,7 +261,7 @@ $messages = array(
 $1',
 'pool-errorunknown' => 'ކޮންމެވެސް ކުށެއް',
 
-# All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage) and the disambiguation template definition (see disambiguations).
+# All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => '{{SITENAME}}ގެ ތަޢާރަފު',
 'aboutpage' => 'Project:ތަޢާރަފު',
 'copyright' => 'ހުރިހާ މާއްދާއެއް $1 ގެ ދަށުން ލިބެން އެބަހުއްޓެވެ.',
index 75b2de9..225a6b2 100644 (file)
@@ -904,7 +904,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite'            => 'About {{SITENAME}}',
 'aboutpage'            => 'Project:About',
-'copyright'            => 'Content is available under $1.',
+'copyright'            => 'Content is available under $1 unless otherwise noted.',
 'copyrightpage'        => '{{ns:project}}:Copyrights',
 'currentevents'        => 'Current events',
 'currentevents-url'    => 'Project:Current events',
@@ -1397,13 +1397,13 @@ The reason given is ''$2''.
 * Intended blockee: $7
 
 You can contact $1 or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the block.
-You cannot use the 'email this user' feature unless a valid email address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.
+You cannot use the \"email this user\" feature unless a valid email address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.
 Your current IP address is $3, and the block ID is #$5.
 Please include all above details in any queries you make.",
-'autoblockedtext'                  => 'Your IP address has been automatically blocked because it was used by another user, who was blocked by $1.
+'autoblockedtext'                  => "Your IP address has been automatically blocked because it was used by another user, who was blocked by $1.
 The reason given is:
 
-:\'\'$2\'\'
+:''$2''
 
 * Start of block: $8
 * Expiry of block: $6
@@ -1411,10 +1411,10 @@ The reason given is:
 
 You may contact $1 or one of the other [[{{MediaWiki:Grouppage-sysop}}|administrators]] to discuss the block.
 
-Note that you may not use the "email this user" feature unless you have a valid email address registered in your [[Special:Preferences|user preferences]] and you have not been blocked from using it.
+Note that you may not use the \"email this user\" feature unless you have a valid email address registered in your [[Special:Preferences|user preferences]] and you have not been blocked from using it.
 
 Your current IP address is $3, and the block ID is #$5.
-Please include all above details in any queries you make.',
+Please include all above details in any queries you make.",
 'blockednoreason'                  => 'no reason given',
 'whitelistedittext'                => 'You have to $1 to edit pages.',
 'confirmedittext'                  => 'You must confirm your email address before editing pages.
@@ -2259,7 +2259,7 @@ To view or search previously uploaded files go to the [[Special:FileList|list of
 
 To include a file in a page, use a link in one of the following forms:
 * '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.jpg]]</nowiki></code>''' to use the full version of the file
-* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.png|200px|thumb|left|alt text]]</nowiki></code>''' to use a 200 pixel wide rendition in a box in the left margin with 'alt text' as description
+* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.png|200px|thumb|left|alt text]]</nowiki></code>''' to use a 200 pixel wide rendition in a box in the left margin with \"alt text\" as description
 * '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code>''' for directly linking to the file without displaying the file",
 'upload-permitted'            => 'Permitted file types: $1.',
 'upload-preferred'            => 'Preferred file types: $1.',
@@ -2972,7 +2972,7 @@ Future changes to this page and its associated talk page will be listed there.',
 'watchmethod-recent'   => 'checking recent edits for watched pages',
 'watchmethod-list'     => 'checking watched pages for recent edits',
 'watchlistcontains'    => 'Your watchlist contains $1 {{PLURAL:$1|page|pages}}.',
-'iteminvalidname'      => "Problem with item '$1', invalid name...",
+'iteminvalidname'      => 'Problem with item "$1", invalid name...',
 'wlnote'               => "Below {{PLURAL:$1|is the last change|are the last '''$1''' changes}} in the last {{PLURAL:$2|hour|'''$2''' hours}}, as of $3, $4.",
 'wlshowlast'           => 'Show last $1 hours $2 days $3',
 'watchlist-options'    => 'Watchlist options',
index a5bd727..ec94bae 100644 (file)
@@ -984,7 +984,7 @@ Contraseña temporal: $2',
 
 # Special:ChangeEmail
 'changeemail' => 'Cambiar la dirección de correo electrónico',
-'changeemail-header' => 'Cambiar la dirección de correo electrónico de la cuenta',
+'changeemail-header' => 'Cambiar la dirección de correo de la cuenta',
 'changeemail-text' => 'Rellena este formulario para cambiar tu dirección de correo electrónico. Debes introducir la contraseña para confirmar este cambio.',
 'changeemail-no-info' => 'Debes iniciar sesión para acceder directamente a esta página.',
 'changeemail-oldemail' => 'Dirección de correo electrónico actual:',
index b34509b..5c7fb19 100644 (file)
@@ -564,7 +564,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'Tietoja {{GRAMMAR:elative|{{SITENAME}}}}',
 'aboutpage' => 'Project:Tietoja',
-'copyright' => 'Sisältö on käytettävissä lisenssillä $1.',
+'copyright' => 'Sisältö on käytettävissä lisenssillä $1, ellei toisin ole mainittu.',
 'copyrightpage' => '{{ns:project}}:Tekijänoikeudet',
 'currentevents' => 'Ajankohtaista',
 'currentevents-url' => 'Project:Ajankohtaista',
index 4683c16..2cda462 100644 (file)
@@ -359,7 +359,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'Um {{SITENAME}}',
 'aboutpage' => 'Project:Um',
-'copyright' => 'Innihald er tøkt undir $1.',
+'copyright' => 'Innihaldið er tøkt undir $1, um ikki annað er viðmerkt.',
 'copyrightpage' => '{{ns:project}}:Upphavsrættur',
 'currentevents' => 'Aktuellar hendingar',
 'currentevents-url' => 'Project:Aktuellar hendingar',
index b762f44..a7a287f 100644 (file)
@@ -638,7 +638,7 @@ $1",
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'À propos de {{SITENAME}}',
 'aboutpage' => 'Project:À propos',
-'copyright' => 'Le contenu est disponible sous licence $1 .',
+'copyright' => 'Le contenu est disponible sous licence $1 sauf mention contraire.',
 'copyrightpage' => '{{ns:project}}:Copyrights',
 'currentevents' => 'Actualités',
 'currentevents-url' => 'Project:Actualités',
index 7b6d14e..fd59f77 100644 (file)
@@ -1332,6 +1332,7 @@ Győződj meg róla, hogy a laptörténet folytonossága megmarad.',
 'compareselectedversions' => 'Kiválasztott változatok összehasonlítása',
 'showhideselectedversions' => 'Kiválasztott változatok láthatóságának beállítása',
 'editundo' => 'visszavonás',
+'diff-empty' => '(Nincs különbség)',
 'diff-multi' => '({{PLURAL:$2|egy|$2}} szerkesztő {{PLURAL:$1|egy|$1}} közbeeső változata nincs mutatva)',
 'diff-multi-manyusers' => '({{PLURAL:$1|Egy közbeeső változat|$1 közbeeső változat}} nincs mutatva, amit $2 szerkesztő módosított)',
 'difference-missing-revision' => 'A(z) "{{PAGENAME}}" nevű oldal #$1 $2 változata nem létezik.
index 8a6605e..544cd56 100644 (file)
@@ -4141,7 +4141,7 @@ $5
 #모든 정규 표현식은 이 줄 위에 넣어 주십시오. 그리고 이 줄은 그대로 두십시오.</pre>',
 
 # Special:Tags
-'tags' => 'ì\98¬ë°\94른 편집 태그',
+'tags' => 'ì\9c í\9a¨í\95\9c 편집 태그',
 'tag-filter' => '[[Special:Tags|태그]] 필터:',
 'tag-filter-submit' => '필터',
 'tag-list-wrapper' => '([[Special:Tags|{{PLURAL:$1|태그}}]]: $2)',
index fb8ebbb..bfb72eb 100644 (file)
@@ -765,11 +765,11 @@ Vergeet niet joew [[Special:Preferences|veurkeuren veur {{SITENAME}}]] an te pas
 'createacct-realname' => 'Echte naam (niet verplicht)',
 'createaccountreason' => 'Reden:',
 'createacct-reason' => 'Reden',
-'createacct-reason-ph' => 'Waorumme jie n aandere gebruker anmaken',
+'createacct-reason-ph' => 'Waorumme je n aandere gebrukerskonto anmaken',
 'createacct-captcha' => 'Veiligheidskontraole',
 'createacct-imgcaptcha-ph' => "Voer de tekste in die'j hierboven zien",
-'createacct-submit' => 'Gebruker anmaken',
-'createacct-another-submit' => 'n Aandere gebruker anmaken',
+'createacct-submit' => 'Gebrukerskonto anmaken',
+'createacct-another-submit' => 'n Aandere gebrukerskonto anmaken',
 'createacct-benefit-heading' => '{{SITENAME}} wörden emaakt deur meensen zo as jie.',
 'createacct-benefit-body1' => 'bewarking{{PLURAL:$1||en}}',
 'createacct-benefit-body2' => '{{PLURAL:$1|zied|ziejen}}',
@@ -833,8 +833,8 @@ Voer de juuste opmaak van t adres in of laot t veld leeg.',
 'accountcreated' => 'Gebrukersprofiel is an-emaakt',
 'accountcreatedtext' => 'De gebrukersnaam veur [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|talk]]) is an-emaakt.',
 'createaccount-title' => 'Gebrukers anmaken veur {{SITENAME}}',
-'createaccount-text' => 'Der hef der ene n gebruker veur $2 an-emaakt op {{SITENAME}} ($4). t Wachtwoord veur "$2" is "$3".
-Meld je noen an en wiezig t wachtwoord.
+'createaccount-text' => 'Der hef der ene n gebruker an-emaakt op {{SITENAME}} ($4), mit de naam $2 en t wachtwoord "$3". 
+Meld je eigen noen an en wiezig t wachtwoord.
 
 Negeer dit bericht as disse gebruker zonder joew toestemming an-emaakt is.',
 'usernamehasherror' => "In n gebrukersnaam ma'j gien hekjen gebruken.",
@@ -1662,7 +1662,7 @@ Disse informasie is zichtbaor veur aandere gebrukers.',
 'action-edit' => 'disse zied bewarken',
 'action-createpage' => 'ziejen schrieven',
 'action-createtalk' => 'overlegziejen anmaken',
-'action-createaccount' => 'disse gebruker anmaken',
+'action-createaccount' => 'disse gebrukerskonto anmaken',
 'action-minoredit' => 'disse bewarking as klein markeren',
 'action-move' => 'disse zied herneumen',
 'action-move-subpages' => 'disse zied en de biebeheurende ziejen die deronder hangen herneumen',
@@ -4023,9 +4023,9 @@ Samen mit dit programma heur je n [{{SERVER}}{{SCRIPTPATH}}/COPYING kopie van de
 'logentry-move-move_redir-noredirect' => '$1 hef de zied $3 {{GENDER:$2|herneumd}} naor $4 over n deurverwiezing heer zonder n deurverwiezing achter te laoten',
 'logentry-patrol-patrol' => '$1 hef versie $4 van de zied $3 op {{GENDER:$2|nao-ekeken}} ezet',
 'logentry-patrol-patrol-auto' => '$1 hef versie $4 van de zied $3 automaties op {{GENDER:$2|nao-ekeken}} ezet',
-'logentry-newusers-newusers' => '$1 hef n gebruker {{GENDER:$2|an-emaakt}}',
-'logentry-newusers-create' => '$1 hef n gebruker {{GENDER:$2|an-emaakt}}',
-'logentry-newusers-create2' => '$1 hef n gebruker $3 {{GENDER:$2|an-emaakt}}',
+'logentry-newusers-newusers' => 'Gebruker $1 is {{GENDER:$2|an-emaakt}}',
+'logentry-newusers-create' => 'Gebruker $1 is {{GENDER:$2|an-emaakt}}',
+'logentry-newusers-create2' => 'Gebruker $3 is {{GENDER:$2|an-emaakt}} an-emaakt deur $1',
 'logentry-newusers-byemail' => 'Gebruker $3 {{GENDER:$2|is}} an-emaakt deur $1 en t wachtwoord is per netpost verstuurd',
 'logentry-newusers-autocreate' => 'De gebruker $1 is automaties {{GENDER:$2|an-emaakt}}',
 'logentry-rights-rights' => '$1 {{GENDER:$2|hef}} groepslidmaotschap veur $3 ewiezigd van $4 naor $5',
index 3dfc3fe..25ddedf 100644 (file)
@@ -209,7 +209,7 @@ $messages = array(
 'jumptonavigation' => 'Faahre-Gnepp',
 'jumptosearch' => 'guck uff',
 
-# All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage) and the disambiguation template definition (see disambiguations).
+# All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'Iwwer {{SITENAME}}',
 'aboutpage' => 'Project:Iwwer_{{SITENAME}}',
 'copyright' => 'Was do drin schdeht iss unner $1 verfiechbar',
index 79b5154..e39ecb9 100644 (file)
@@ -674,9 +674,9 @@ A dovrìa felo si për asar chiel a l'ha partagiaje con cheidun o si sò cont a
 
 # Edit pages
 'summary' => 'Resumé:',
-'subject' => 'Sogèt:',
+'subject' => 'Sogèt/antestassion:',
 'minoredit' => "Costa a l'é na modìfica cita",
-'watchthis' => "Ten sot euj st'artìcol-sì",
+'watchthis' => 'Ten-e sot euj costa pàgina-sì',
 'savearticle' => 'Salva sta pàgina',
 'preview' => 'Preuva',
 'showpreview' => 'Mostra na preuva',
index 60af1e1..db3565d 100644 (file)
@@ -537,7 +537,7 @@ $messages = array(
 'unprotectthispage' => 'Alterar a proteção desta página',
 'newpage' => 'Página nova',
 'talkpage' => 'Dialogar sobre esta página',
-'talkpagelinktext' => 'disc',
+'talkpagelinktext' => 'Discussão',
 'specialpage' => 'Página especial',
 'personaltools' => 'Ferramentas pessoais',
 'postcomment' => 'Nova seção',
@@ -574,7 +574,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'Sobre {{SITENAME}}',
 'aboutpage' => 'Project:Sobre',
-'copyright' => 'Conteúdo disponível sob $1.',
+'copyright' => 'Conteúdo disponível sob $1, salvo indicação em contrário.',
 'copyrightpage' => '{{ns:project}}:Direitos_de_autor',
 'currentevents' => 'Eventos atuais',
 'currentevents-url' => 'Project:Eventos atuais',
index 2653d44..faaa635 100644 (file)
@@ -4408,7 +4408,9 @@ MediaWiki распространяется в надежде, что она бу
 
 # Limit report
 'limitreport-cputime' => 'Использование времени процессора',
+'limitreport-cputime-value' => '$1 {{PLURAL:$1|секунда|секунды|секунд}}',
 'limitreport-walltime' => 'Использование в режиме реального времени',
+'limitreport-walltime-value' => '$1 {{PLURAL:$1|секунда|секунды|секунд}}',
 'limitreport-ppgeneratednodes' => 'Количество сгенерированных препроцессором узлов',
 'limitreport-postexpandincludesize-value' => '$1/$2 байт',
 'limitreport-templateargumentsize' => 'Размер аргумента шаблона',
index 1f90791..26d4ce5 100644 (file)
@@ -201,8 +201,8 @@ $messages = array(
 'tog-extendwatchlist' => 'මෑත වෙනස්වීම් පමණක් නොව, අදාළ සියළු වෙනස්වීම් දක්වා පෙන්වන අයුරින් මුර-ලැයිස්තුව පුළුල් කරන්න',
 'tog-usenewrc' => 'මෑත වෙනස්වීම් සහ මුර ලැයිස්තුව හී පිටුව අනුව සමූහ වෙනස්වීම් (ජාවාස්ක්‍රිප්ට් ඇවැසිය)',
 'tog-numberheadings' => 'ශීර්ෂ-නාම ස්වයංක්‍රීයව අංකනය කරන්න',
-'tog-showtoolbar' => 'සංස්කරණ මෙවලම්තීරුව පෙන්වන්න (ජාවාස්ක්‍රිප්ට්)',
-'tog-editondblclick' => 'ද්විත්ව-ක්ලික් කිරීම මගින් පිටු සංස්කරණය අරඹන්න (ජාවාස්ක්‍රිප්ට්)',
+'tog-showtoolbar' => 'සංස්කරණ මෙවලම්තීරුව පෙන්වන්න',
+'tog-editondblclick' => 'ද්විත්ව-ක්ලික් කිරීම මගින් පිටු සංස්කරණය අරඹන්න',
 'tog-editsection' => '[සංස්කරණ] සබැඳියාවන් මගින් ඡේද සංස්කරණය සක්‍රීය කරන්න',
 'tog-editsectiononrightclick' => 'ඡේද ශීර්ෂ මත දකුණු-ක්ලික් කිරීමෙන් ඡේද සංස්කරණය සක්‍රීය කරන්න (ජාවාස්ක්‍රිප්ට්)',
 'tog-showtoc' => 'පටුන පෙන්වන්න ( තුනකට වඩා වැඩියෙන් ශීර්ෂ-නාම අඩංගු පිටු සඳහා)',
@@ -971,7 +971,7 @@ $2
 'nocreate-loggedin' => '{{SITENAME}} හි නව පිටු තැනීමට අවසරයක් ඔබ හට ප්‍රදානය කොට නොමැත.',
 'sectioneditnotsupported-title' => 'කොටසක් සංස්කරණය කිරීම සඳහා සහාය නොදක්වයි',
 'sectioneditnotsupported-text' => 'මෙම පිටුවේදී කොටසක් සංස්කරණය කිරීම සඳහා සහාය නොදක්වයි',
-'permissionserrors' => 'à¶\85à·\80à·\83රයනà·\8a à¶´à·\92à·\85à·\92බඳ à¶¯à·\9dà·\82යනà·\8a à¶´à·\80තà·\93',
+'permissionserrors' => 'à¶\85à·\80à·\83රදà·\93මà·\9a à¶¯à·\9dà·\82යà¶\9aà·\92',
 'permissionserrorstext' => 'පහත දැක්වෙන {{PLURAL:$1|හේතුව|හේතූන්}} නිසා, ඔබ හට එය සිදුකිරීමට අවසර ලබා දීමට නොහැක:',
 'permissionserrorstext-withaction' => 'පහත {{PLURAL:$1|හේතුව|හේතු}} නිසා, ඔබ හට $2 සඳහා අවසර නොමැත:',
 'recreate-moveddeleted-warn' => "'''අවවාදයයි: පෙරදී මකාදැමුණු පිටුවක් ඔබ විසින් යළි-තනමින් පවතියි.'''
@@ -1372,7 +1372,7 @@ HTML ටැගයන් පිරික්සන්න.',
 'prefs-signature' => 'අත්සන',
 'prefs-dateformat' => 'දින ආකෘතිය',
 'prefs-timeoffset' => 'වේලා හිලව්ව',
-'prefs-advancedediting' => 'à·\83à·\8fමà·\8fනà·\8aâ\80\8dය',
+'prefs-advancedediting' => 'පà·\8aâ\80\8dරධà·\8fන à·\80à·\92à¶\9aලà·\8aපයනà·\8a',
 'prefs-advancedrc' => 'වැඩිදුර සැකසුම් තෝරාගැනීම',
 'prefs-advancedrendering' => 'වැඩිදුර සැකසුම් තෝරාගැනීම',
 'prefs-advancedsearchoptions' => 'ප්‍රගත විකල්පයන්',
index 797b070..703abf7 100644 (file)
@@ -1696,7 +1696,7 @@ Musí obsahovať menej ako $1 {{PLURAL:$1|znak|znaky|znakov}}.',
 'rc_categories_any' => 'akékoľvek',
 'rc-change-size-new' => '$1 {{PLURAL:$1|bajt|bajty|bajtov}} po zmene',
 'newsectionsummary' => '/* $1 */ nová sekcia',
-'rc-enhanced-expand' => 'Zobraziť podrobnosti (vyžaduje JavaScript)',
+'rc-enhanced-expand' => 'Zobraziť podrobnosti',
 'rc-enhanced-hide' => 'Skryť podrobnosti',
 'rc-old-title' => 'pôvodne vytvorené ako "$1"',
 
index 2ab3fa2..6d48fdc 100644 (file)
@@ -2136,7 +2136,7 @@ $1',
 # File description page
 'file-anchor-link' => 'Датотека',
 'filehist' => 'Историја датотеке',
-'filehist-help' => 'Кликните на датум/време да видите тадашње издање датотеке.',
+'filehist-help' => 'Кликните на датум/време да видите тадашњу верзију датотеке.',
 'filehist-deleteall' => 'обриши све',
 'filehist-deleteone' => 'обриши',
 'filehist-revert' => 'врати',
@@ -3523,7 +3523,7 @@ Variants for Chinese language
 
 # Metadata
 'metadata' => 'Метаподаци',
-'metadata-help' => 'Ова датотека садржи додатне податке који вероватно долазе од дигигалних фотоапарата или скенера.
+'metadata-help' => 'Ова датотека садржи додатне податке који вероватно долазе од дигиталног фотоапарата или скенера.
 Ако је првобитно стање датотеке промењено, могуће је да неки детаљи не описују измењену датотеку.',
 'metadata-expand' => 'Прикажи детаље',
 'metadata-collapse' => 'Сакриј детаље',
index 8159d4b..7a89772 100644 (file)
@@ -2007,7 +2007,7 @@ Probajte kasnije kada bude manje opterećenje.',
 # File description page
 'file-anchor-link' => 'Datoteka',
 'filehist' => 'Istorija datoteke',
-'filehist-help' => 'Kliknite na datum/vreme da vidite tadašnje izdanje datoteke.',
+'filehist-help' => 'Kliknite na datum/vreme da vidite tadašnju verziju datoteke.',
 'filehist-deleteall' => 'obriši sve',
 'filehist-deleteone' => 'obriši',
 'filehist-revert' => 'vrati',
@@ -3359,7 +3359,7 @@ Variants for Chinese language
 
 # Metadata
 'metadata' => 'Metapodaci',
-'metadata-help' => 'Ova datoteka sadrži dodatne podatke koji verovatno dolaze od digigalnih fotoaparata ili skenera.
+'metadata-help' => 'Ova datoteka sadrži dodatne podatke koji verovatno dolaze od digitalnog fotoaparata ili skenera.
 Ako je prvobitno stanje datoteke promenjeno, moguće je da neki detalji ne opisuju izmenjenu datoteku.',
 'metadata-expand' => 'Prikaži detalje',
 'metadata-collapse' => 'Sakrij detalje',
index dc4d9a4..1dd34a5 100644 (file)
@@ -23,6 +23,7 @@
  * @author Habjchen
  * @author Hangsna
  * @author Hannibal
+ * @author Haxpett
  * @author Jon Harald Søby
  * @author Jopparn
  * @author Kaganer
@@ -1355,6 +1356,7 @@ Se till att sidhistorikens kontinuitet behålls när du sammanfogar historik.',
 'mergehistory-comment' => 'Infogade [[:$1]] i [[:$2]]: $3',
 'mergehistory-same-destination' => 'Käll- och målsidor kan inte vara samma',
 'mergehistory-reason' => 'Anledning:',
+'mergehistory-revisionrow' => '$1($2) $3 . .$4 $5 $6',
 
 # Merge log
 'mergelog' => 'Sammanfogningslogg',
index bc0f3eb..caaf42c 100644 (file)
@@ -614,7 +614,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'Про {{grammar:accusative|{{SITENAME}}}}',
 'aboutpage' => 'Project:Про',
-'copyright' => 'Ð\92мÑ\96Ñ\81Ñ\82 Ð´Ð¾Ñ\81Ñ\82Ñ\83пний Ð·Ð³Ñ\96дно Ð· $1.',
+'copyright' => 'Ð\92мÑ\96Ñ\81Ñ\82 Ð´Ð¾Ñ\81Ñ\82Ñ\83пний Ð½Ð° Ñ\83моваÑ\85 $1, Ñ\8fкÑ\89о Ð½Ðµ Ð²ÐºÐ°Ð·Ð°Ð½Ð¾ Ñ\96нÑ\88е.',
 'copyrightpage' => '{{ns:project}}:Авторське право',
 'currentevents' => 'Поточні події',
 'currentevents-url' => 'Project:Поточні події',
diff --git a/tests/phpunit/includes/HtmlFormatterTest.php b/tests/phpunit/includes/HtmlFormatterTest.php
new file mode 100644 (file)
index 0000000..a37df74
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @group HtmlFormatter
+ */
+class HtmlFormatterTest extends MediaWikiTestCase {
+       /**
+        * @dataProvider getHtmlData
+        */
+       public function testTransform( $input, $expected, $callback = false ) {
+               $input = self::normalize( $input );
+               $formatter = new HtmlFormatter( HtmlFormatter::wrapHTML( $input ) );
+               if ( $callback ) {
+                       $callback( $formatter );
+               }
+               $formatter->filterContent();
+               $html = $formatter->getText();
+               $this->assertEquals( self::normalize( $expected ), self::normalize( $html ) );
+       }
+
+       private static function normalize( $s ) {
+               return str_replace( "\n", '',
+                       str_replace( "\r", '', $s ) // "yay" to Windows!
+               );
+       }
+
+       public function getHtmlData() {
+               $removeImages = function( HtmlFormatter $f ) {
+                       $f->setRemoveMedia();
+               };
+               $removeTags = function( HtmlFormatter $f ) {
+                       $f->remove( array( 'table', '.foo', '#bar', 'div.baz' ) );
+               };
+               $flattenSomeStuff = function( HtmlFormatter $f ) {
+                       $f->flatten( array( 's', 'div' ) );
+               };
+               $flattenEverything = function( HtmlFormatter $f ) {
+                       $f->flattenAllTags();
+               };
+               return array(
+                       // remove images if asked
+                       array(
+                               '<img src="/foo/bar.jpg" alt="Blah"/>',
+                               '',
+                               $removeImages,
+                       ),
+                       // basic tag removal
+                       array(
+                               '<table><tr><td>foo</td></tr></table><div class="foo">foo</div><div class="foo quux">foo</div><span id="bar">bar</span>
+<strong class="foo" id="bar">foobar</strong><div class="notfoo">test</div><div class="baz"/>
+<span class="baz">baz</span>',
+
+                               '<div class="notfoo">test</div>
+<span class="baz">baz</span>',
+                               $removeTags,
+                       ),
+                       // don't flatten tags that start like chosen ones
+                       array(
+                               '<div><s>foo</s> <span>bar</span></div>',
+                               'foo <span>bar</span>',
+                               $flattenSomeStuff,
+                       ),
+                       // total flattening
+                       array(
+                               '<div style="foo">bar<sup>2</sup></div>',
+                               'bar2',
+                               $flattenEverything,
+                       ),
+                       // UTF-8 preservation and security
+                       array(
+                               '<span title="&quot; \' &amp;">&lt;Тест!&gt;</span> &amp;&lt;&#38;&#0038;&#x26;&#x026;',
+                               '<span title="&quot; \' &amp;">&lt;Тест!&gt;</span> &amp;&lt;&amp;&amp;&amp;&amp;',
+                       ),
+                       // https://bugzilla.wikimedia.org/show_bug.cgi?id=53086
+                       array(
+                               'Foo<sup id="cite_ref-1" class="reference"><a href="#cite_note-1">[1]</a></sup> <a href="/wiki/Bar" title="Bar" class="mw-redirect">Bar</a>',
+                               'Foo<sup id="cite_ref-1" class="reference"><a href="#cite_note-1">[1]</a></sup> <a href="/wiki/Bar" title="Bar" class="mw-redirect">Bar</a>',
+                       ),
+               );
+       }
+}