Merge "[MCR] Introduce RevisionSlotsUpdate."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 9 May 2018 13:27:08 +0000 (13:27 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 9 May 2018 13:27:08 +0000 (13:27 +0000)
309 files changed:
.phpcs.xml
Gruntfile.js
RELEASE-NOTES-1.31
RELEASE-NOTES-1.32
autoload.php
composer.json
docs/database.txt
includes/CommentStore.php
includes/DefaultSettings.php
includes/EditPage.php
includes/GlobalFunctions.php
includes/MediaWiki.php
includes/OutputPage.php
includes/Revision.php
includes/Setup.php
includes/Storage/RevisionStore.php
includes/Storage/SqlBlobStore.php
includes/api/ApiLogin.php
includes/api/ApiParse.php
includes/api/i18n/es.json
includes/api/i18n/he.json
includes/api/i18n/ja.json
includes/api/i18n/zh-hant.json
includes/api/i18n/zh-hk.json [new file with mode: 0644]
includes/changes/RecentChange.php
includes/dao/DBAccessBase.php
includes/deferred/DeferredUpdates.php
includes/deferred/LinksUpdate.php
includes/deferred/SqlDataUpdate.php [deleted file]
includes/diff/DiffEngine.php
includes/diff/WordAccumulator.php
includes/filerepo/FileRepo.php
includes/filerepo/ForeignAPIRepo.php
includes/filerepo/ForeignDBViaLBRepo.php
includes/filerepo/LocalRepo.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/File.php
includes/filerepo/file/ForeignAPIFile.php
includes/filerepo/file/ForeignDBFile.php
includes/filerepo/file/LocalFile.php
includes/filerepo/file/OldLocalFile.php
includes/htmlform/fields/HTMLExpiryField.php
includes/htmlform/fields/HTMLTitleTextField.php
includes/installer/WebInstallerOutput.php
includes/installer/i18n/cs.json
includes/installer/i18n/eu.json
includes/installer/i18n/fi.json
includes/installer/i18n/it.json
includes/installer/i18n/ja.json
includes/installer/i18n/lb.json
includes/installer/i18n/ml.json
includes/installer/i18n/nb.json
includes/installer/i18n/zh-hk.json
includes/interwiki/Interwiki.php
includes/libs/filebackend/HTTPFileStreamer.php
includes/libs/rdbms/ChronologyProtector.php
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/lbfactory/ILBFactory.php
includes/libs/rdbms/lbfactory/LBFactory.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/logging/LogEntry.php
includes/logging/LogFormatter.php
includes/logging/LogPager.php
includes/mail/UserMailer.php
includes/media/BMP.php [deleted file]
includes/media/Bitmap.php [deleted file]
includes/media/BitmapHandler.php [new file with mode: 0644]
includes/media/BitmapHandler_ClientOnly.php [new file with mode: 0644]
includes/media/Bitmap_ClientOnly.php [deleted file]
includes/media/BmpHandler.php [new file with mode: 0644]
includes/media/DjVu.php [deleted file]
includes/media/DjVuHandler.php [new file with mode: 0644]
includes/media/ExifBitmap.php [deleted file]
includes/media/ExifBitmapHandler.php [new file with mode: 0644]
includes/media/GIF.php [deleted file]
includes/media/GIFHandler.php [new file with mode: 0644]
includes/media/Jpeg.php [deleted file]
includes/media/JpegHandler.php [new file with mode: 0644]
includes/media/PNG.php [deleted file]
includes/media/PNGHandler.php [new file with mode: 0644]
includes/media/SVG.php [deleted file]
includes/media/SvgHandler.php [new file with mode: 0644]
includes/media/Tiff.php [deleted file]
includes/media/TiffHandler.php [new file with mode: 0644]
includes/media/WebP.php [deleted file]
includes/media/WebPHandler.php [new file with mode: 0644]
includes/parser/BlockLevelPass.php
includes/parser/MWTidy.php
includes/parser/ParserOutput.php
includes/parser/Preprocessor_DOM.php
includes/parser/Preprocessor_Hash.php
includes/parser/Sanitizer.php
includes/preferences/DefaultPreferencesFactory.php
includes/resourceloader/ResourceLoaderSkinModule.php
includes/search/SearchMySQL.php
includes/skins/Skin.php
includes/skins/SkinTemplate.php
includes/specials/SpecialActiveusers.php
includes/specials/SpecialAutoblockList.php
includes/specials/SpecialBlockList.php
includes/specials/SpecialBotPasswords.php
includes/specials/SpecialEditWatchlist.php
includes/specials/SpecialLog.php
includes/specials/SpecialPasswordReset.php
includes/specials/SpecialPreferences.php
includes/specials/SpecialRedirect.php
includes/specials/SpecialResetTokens.php
includes/specials/SpecialStatistics.php
includes/specials/SpecialUnblock.php
includes/specials/SpecialUserrights.php
includes/specials/formfields/Licenses.php
includes/specials/forms/EditWatchlistNormalHTMLForm.php
includes/specials/forms/PreferencesForm.php
includes/specials/forms/PreferencesFormLegacy.php [new file with mode: 0644]
includes/specials/forms/PreferencesFormOOUI.php [new file with mode: 0644]
includes/specials/pagers/ImageListPager.php
includes/tidy/Balancer.php [deleted file]
includes/tidy/Html5Depurate.php [deleted file]
includes/tidy/Html5Internal.php [deleted file]
includes/tidy/RaggettBase.php
includes/user/BotPassword.php
includes/user/User.php
includes/utils/AutoloadGenerator.php
includes/watcheditem/WatchedItemStoreInterface.php
languages/classes/LanguageCrh.php
languages/data/CrhExceptions.php
languages/data/Names.php
languages/i18n/abs.json [new file with mode: 0644]
languages/i18n/ace.json
languages/i18n/ar.json
languages/i18n/arq.json
languages/i18n/as.json
languages/i18n/ast.json
languages/i18n/az.json
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bg.json
languages/i18n/bho.json
languages/i18n/bn.json
languages/i18n/bqi.json
languages/i18n/bs.json
languages/i18n/ca.json
languages/i18n/ce.json
languages/i18n/ckb.json
languages/i18n/cs.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/el.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/eu.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/gcr.json
languages/i18n/gl.json
languages/i18n/ha.json
languages/i18n/he.json
languages/i18n/hr.json
languages/i18n/hsb.json
languages/i18n/hu.json
languages/i18n/hy.json
languages/i18n/id.json
languages/i18n/ig.json
languages/i18n/inh.json
languages/i18n/io.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/ko.json
languages/i18n/li.json
languages/i18n/lt.json
languages/i18n/lv.json
languages/i18n/lzh.json
languages/i18n/min.json
languages/i18n/mk.json
languages/i18n/ml.json
languages/i18n/nah.json
languages/i18n/nb.json
languages/i18n/nds-nl.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/oc.json
languages/i18n/ps.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/rm.json
languages/i18n/ro.json
languages/i18n/ru.json
languages/i18n/sat.json
languages/i18n/sc.json
languages/i18n/sd.json
languages/i18n/shy-latn.json [new file with mode: 0644]
languages/i18n/skr-arab.json
languages/i18n/sl.json
languages/i18n/sr-ec.json
languages/i18n/sv.json
languages/i18n/szl.json
languages/i18n/ta.json
languages/i18n/tg-cyrl.json
languages/i18n/tt-cyrl.json
languages/i18n/ur.json
languages/i18n/vi.json
languages/i18n/zgh.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
languages/i18n/zh-hk.json
languages/messages/MessagesAbs.php [new file with mode: 0644]
languages/messages/MessagesKo_kp.php
maintenance/getReplicaServer.php
maintenance/initEditCount.php
maintenance/jsduck/eg-iframe.html
maintenance/lag.php
maintenance/mssql/tables.sql
maintenance/populateRevisionLength.php
maintenance/postgres/archives/patch-drop-ar_text.sql
maintenance/storage/dumpRev.php
maintenance/tables.sql
maintenance/updateSpecialPages.php
package.json
resources/Resources.php
resources/lib/CLDRPluralRuleParser/CLDRPluralRuleParser.js [new file with mode: 0644]
resources/lib/jquery.ui/PATCHES
resources/lib/jquery.ui/jquery.ui.mouse.js
resources/lib/qunitjs/qunit.css
resources/lib/qunitjs/qunit.js
resources/src/jquery/jquery.makeCollapsible.css
resources/src/jquery/jquery.makeCollapsible.js
resources/src/jquery/jquery.makeCollapsible.styles.less [new file with mode: 0644]
resources/src/jquery/jquery.tablesorter.js
resources/src/jquery/jquery.tablesorter.less
resources/src/jquery/jquery.tablesorter.styles.less [new file with mode: 0644]
resources/src/mediawiki.action/mediawiki.action.edit.styles.less
resources/src/mediawiki.legacy/oldshared.css
resources/src/mediawiki.legacy/wikibits.js
resources/src/mediawiki.libs.jpegmeta/export.js [new file with mode: 0644]
resources/src/mediawiki.libs.jpegmeta/jpegmeta.js [new file with mode: 0644]
resources/src/mediawiki.libs.pluralruleparser/export.js [new file with mode: 0644]
resources/src/mediawiki.libs/CLDRPluralRuleParser.js [deleted file]
resources/src/mediawiki.libs/mediawiki.libs.jpegmeta.js [deleted file]
resources/src/mediawiki.special/mediawiki.special.preferences.confirmClose.js
resources/src/mediawiki.special/mediawiki.special.preferences.editfont.js [new file with mode: 0644]
resources/src/mediawiki.special/mediawiki.special.preferences.styles.css
resources/src/mediawiki.special/mediawiki.special.preferences.styles.legacy.css [new file with mode: 0644]
resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js
resources/src/mediawiki.special/mediawiki.special.preferences.tabs.legacy.js [new file with mode: 0644]
resources/src/mediawiki.special/mediawiki.special.preferences.timezone.js
resources/src/mediawiki.special/mediawiki.special.upload.js
resources/src/mediawiki.widgets.visibleLengthLimit/mediawiki.widgets.visibleLengthLimit.js
resources/src/mediawiki/api.js
resources/src/mediawiki/mediawiki.feedback.css
resources/src/mediawiki/mediawiki.jqueryMsg.js
resources/src/mediawiki/mediawiki.js
resources/src/mediawiki/mediawiki.sectionAnchor.css [deleted file]
resources/src/mediawiki/mediawiki.util.js
resources/src/moment-dmy.js [deleted file]
resources/src/moment-global.js [deleted file]
resources/src/moment-locale-overrides.js [deleted file]
resources/src/moment/moment-dmy.js [new file with mode: 0644]
resources/src/moment/moment-global.js [new file with mode: 0644]
resources/src/moment/moment-locale-overrides.js [new file with mode: 0644]
resources/src/startup.js
tests/parser/ParserTestRunner.php
tests/phan/config.php
tests/phpunit/includes/MovePageTest.php
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/SampleTest.php
tests/phpunit/includes/Storage/RevisionStoreDbTest.php
tests/phpunit/includes/Storage/RevisionStoreTest.php
tests/phpunit/includes/api/ApiParseTest.php
tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php
tests/phpunit/includes/db/LBFactoryTest.php
tests/phpunit/includes/db/LoadBalancerTest.php
tests/phpunit/includes/deferred/DeferredUpdatesTest.php
tests/phpunit/includes/libs/CSSMinTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php
tests/phpunit/includes/logging/LogFormatterTest.php
tests/phpunit/includes/parser/SanitizerTest.php
tests/phpunit/includes/preferences/DefaultPreferencesFactoryTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderSkinModuleTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderTest.php
tests/phpunit/includes/site/DBSiteStoreTest.php
tests/phpunit/includes/skins/SkinTemplateTest.php
tests/phpunit/includes/skins/SkinTest.php [new file with mode: 0644]
tests/phpunit/includes/specials/SpecialEditWatchlistTest.php
tests/phpunit/includes/tidy/BalancerTest.php [deleted file]
tests/phpunit/includes/utils/ClassCollectorTest.php
tests/phpunit/languages/classes/LanguageCrhTest.php
tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js
tests/selenium/.eslintrc.json
tests/selenium/README.md
tests/selenium/pageobjects/createaccount.page.js
tests/selenium/pageobjects/delete.page.js
tests/selenium/pageobjects/edit.page.js
tests/selenium/pageobjects/history.page.js
tests/selenium/pageobjects/page.js
tests/selenium/pageobjects/preferences.page.js
tests/selenium/pageobjects/restore.page.js
tests/selenium/pageobjects/userlogin.page.js
tests/selenium/selenium.sh
tests/selenium/specs/page.js
tests/selenium/specs/user.js
tests/selenium/wdio.conf.js

index 31e6eeb..d43a281 100644 (file)
@@ -17,7 +17,6 @@
                <exclude name="MediaWiki.WhiteSpace.SpaceBeforeSingleLineComment.SingleSpaceBeforeSingleLineComment" />
                <exclude name="MediaWiki.Usage.DbrQueryUsage.DbrQueryFound" />
                <exclude name="MediaWiki.Usage.ExtendClassUsage.FunctionVarUsage" />
-               <exclude name="MediaWiki.Usage.ForbiddenFunctions.assert" />
                <exclude name="MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals" />
                <exclude name="MediaWiki.Files.ClassMatchesFilename.WrongCase" />
                <exclude name="MediaWiki.Files.ClassMatchesFilename.NotMatch" />
index 69a123c..2f55868 100644 (file)
@@ -13,7 +13,6 @@ module.exports = function ( grunt ) {
        grunt.loadNpmTasks( 'grunt-jsonlint' );
        grunt.loadNpmTasks( 'grunt-karma' );
        grunt.loadNpmTasks( 'grunt-stylelint' );
-       grunt.loadNpmTasks( 'grunt-webdriver' );
 
        karmaProxy[ wgScriptPath ] = {
                target: wgServer + wgScriptPath,
@@ -29,7 +28,7 @@ module.exports = function ( grunt ) {
                                '!resources/lib/**',
                                '!resources/src/jquery.tipsy/**',
                                '!resources/src/jquery/jquery.farbtastic.js',
-                               '!resources/src/mediawiki.libs/**',
+                               '!resources/src/mediawiki.libs.jpegmeta/**',
                                // Third-party code of PHPUnit coverage report
                                '!tests/coverage/**',
                                '!vendor/**',
@@ -104,15 +103,7 @@ module.exports = function ( grunt ) {
                                        return require( 'path' ).join( dest, src.replace( 'resources/', '' ) );
                                }
                        }
-               },
-
-               // Configure WebdriverIO task
-               webdriver: {
-                       test: {
-                               configFile: './tests/selenium/wdio.conf.js'
-                       }
                }
-
        } );
 
        grunt.registerTask( 'assert-mw-env', function () {
index 1386184..a702451 100644 (file)
@@ -5,61 +5,54 @@ THIS IS NOT A RELEASE YET
 MediaWiki 1.31 is an alpha-quality branch and is not recommended for use in
 production.
 
-=== Important pre-upgrade notes for 1.31 ===
-* If you're using MySQL, SQLite, or MSSQL, are not using update.php to apply
-  schema changes, and cannot have downtime to run migrateArchiveText.php and
-  apply patch-drop-ar_text.sql manually, you'll have to apply a default value
-  to the ar_text and ar_flags columns of the archive table or make those
-  columns nullable before upgrading to MediaWiki 1.31.
-  maintenance/archives/patch-nullable-ar_text.sql shows how to do this for MySQL.
-
 === Configuration changes in 1.31 ===
 * $wgEnableAPI and $wgEnableWriteAPI are now deprecated and will be removed in
   a future version. The API is now considered to be stable, secure and
   essential.
-* $wgUsejQueryThree was removed, as it is now the default. This was documented as a
-  temporary variable during the migration period, deprecated since 1.29.
+* $wgUsejQueryThree was removed, as it is now the default. This was documented
+  as a temporary variable during the migration period, deprecated since 1.29.
 * $wgLogoHD has been updated to support svg images and uses $wgLogo where
   possible for fallback images such as png.
-* (T44246) $wgFilterLogTypes will no longer ignore 'patrol' when user does
-  not have the right to mark things patrolled.
+* (T44246) $wgFilterLogTypes will no longer ignore 'patrol' when user does not
+  have the right to mark things patrolled.
 * Wikis that contain imported revisions or CentralAuth global blocks should run
   maintenance/cleanupUsersWithNoId.php.
-* $wgResourceLoaderMinifierStatementsOnOwnLine and $wgResourceLoaderMinifierMaxLineLength
-  were removed (deprecated since 1.27).
-* (T180921) $wgReferrerPolicy now supports having fallbacks for browsers that are not
-  using the latest version of the Referrer Policy specification.
-* $wgFragmentMode is now set to [ 'legacy', 'html5' ] by default. This is a first step of
-  migration to human-readable section IDs that will later result in 'html5' being the
-  default mode.
+* The configuration settings $wgResourceLoaderMinifierStatementsOnOwnLine and
+  $wgResourceLoaderMinifierMaxLineLength, deprecated since 1.27, were removed.
+* (T180921) $wgReferrerPolicy now supports having fallbacks for browsers that
+  are not using the latest version of the Referrer Policy specification.
+* $wgFragmentMode is now set to [ 'legacy', 'html5' ] by default. This is a
+  first step of migration to human-readable section IDs that will later result
+  in 'html5' being the default mode.
 * CACHE_ACCEL now only supports APC(u) or WinCache. XCache support was removed
   as upstream is inactive and has no plans to move to PHP 7.
 * The old CategorizedRecentChanges feature, including its related configuration
   option $wgAllowCategorizedRecentChanges, has been removed.
-* (T188472) The 'comma' value for $wgArticleCountMethod is no longer supported for
-  performance reasons, and installations with this setting will now work as if it
-  was configured with 'any'.
-* (T185753) MediaWiki now defaults to using RemexHtml to tidy up user input, rather than
-  being off by default. If you wish to disable HTML tidying entirely, set $wgTidyConfig
-  to null; if you wish to use the old, deprecated Tidy external binary, both
-  set $wgTidyConfig to null and also set $wgUseTidy to true.
+* (T188472) The 'comma' value for $wgArticleCountMethod is no longer supported
+  for performance reasons, and installations with this setting will now work as
+  if it was configured with 'any'.
+* (T185753) MediaWiki now defaults to using RemexHtml to tidy up user input,
+  rather than being off by default. If you wish to disable HTML tidying
+  entirely, set $wgTidyConfig to null; if you wish to use the old, deprecated
+  Tidy external binary, both set $wgTidyConfig to null and $wgUseTidy to true.
 * $wgLogAutopatrol now defaults to false instead of true.
 * $wgValidateAllHtml was removed and will be ignored.
-* $wgScriptExtension was removed (deprecated and ignored since 1.25).
-  See 1.25 release notes for more information.
+* $wgScriptExtension, deprecated and ignored since 1.25, was removed. See the
+  1.25 release notes for more information.
 * $wgUseAjax is now marked as deprecated, just like the deprecated AJAX
   framework that it enables. Some extensions mistakenly used this to check
   whether any AJAX functionality at all should be enabled, further making this
   problematic to retain.
 
 === New features in 1.31 ===
-* (T76554) User sub-pages named ….json are now protected in the same way that ….js
-  and ….css pages are, so that configuration options can safely be placed there.
-* Wikimedia\Rdbms\IDatabase->select() and similar methods now support
-  joins with parentheses for grouping.
+* (T76554) User sub-pages named ….json are now protected in the same way that
+  ….js and ….css pages are, so that configuration options can safely be placed
+  there.
+* Wikimedia\Rdbms\IDatabase->select() and similar methods now support joins
+  with parentheses for grouping.
 * As a first pass in standardizing dialog boxes across the MediaWiki product,
-  Html class now provides helper methods for messageBox, successBox, errorBox and
-  warningBox generation.
+  Html class now provides helper methods for messageBox, successBox, errorBox
+  and warningBox generation.
 * (T9240) Imports will now record unknown (and, optionally, known) usernames in
   a format like "iw>Example".
 * (T20209) Linker (used on history pages, log pages, and so on) will display
@@ -85,9 +78,9 @@ production.
     soon as any necessary extensions are updated.
   * Most code accessing rows for logged actions from the database should use
     the relevant getQueryInfo() methods to get the information needed to build
-    the SQL query. The ActorMigration class may also be used to get feature-flagged
-    information needed to access actor-related fields during the migration
-    period.
+    the SQL query. The ActorMigration class may also be used to get feature
+    -flagged information needed to access actor-related fields during the
+    migration period.
 * Added Wikimedia\Rdbms\IDatabase::cancelAtomic(), to roll back an atomic
   section without having to roll back the whole transaction.
 * Wikimedia\Rdbms\IDatabase::doAtomicSection(), non-native ::insertSelect(),
@@ -98,21 +91,21 @@ production.
   extensions. Pass --with-extensions to enable that feature.
 * (T184791) rc_patrolled now has three states: "0" for unpatrolled,
   "1" for manually patrolled and "2" for autopatrolled actions.
-* Extensions can now set their type to "editor" if they provide an editor
-  or enhance the editing experience.
-* Extensions can use a PSR-4 autoloader by setting an "AutoloadNamespaces" property
-  in extension.json. See
-  <https://www.mediawiki.org/wiki/Manual:Extension.json/Schema#AutoloadNamespaces>
+* Extensions can now set their type to "editor" if they provide an editor or
+  enhance the editing experience.
+* Extensions can use a PSR-4 autoloader by setting an "AutoloadNamespaces"
+  property in extension.json. See the documentation at
+  <https://mediawiki.org/wiki/Manual:Extension.json/Schema#AutoloadNamespaces>
   for more details and an example.
+* (T19099) Tabs which link to pages that don't exist (like those to uncreated
+  discussion pages) now have a tooltip to indicate state, not just colour.
 
 === External library changes in 1.31 ===
 
 ==== Upgraded external libraries ====
 * Updated jquery.chosen from v0.9.14 to v1.8.2.
-* Updated composer/spdx-licenses from 1.1.4 to
-  1.3.0 (development dependency).
-* Updated nikic/php-parser from 2.1.0 to 3.1.3
-  (development dependency).
+* Updated composer/spdx-licenses from 1.1.4 to 1.3.0 (development dependency).
+* Updated nikic/php-parser from 2.1.0 to 3.1.3 (development dependency).
 * Updated wikimedia/ip-set from 1.1.0 to 1.2.0.
 * Updated wikimedia/relpath from 2.0.0 to 2.1.1.
 * Updated wikimedia/running-stat from 1.1.0 to 1.2.0.
@@ -120,11 +113,10 @@ production.
 * Updated mediawiki/at-ease from 1.1.0 to 1.2.0.
 * Updated wikimedia/php-session-serializer from 1.0.4 to 1.0.6.
 * Updated wikimedia/remex-html from 1.0.2 to 1.0.3.
-* 
+* Updated wikimedia/html-formatter from 1.0.1 to 1.0.2.
 
 ==== New external libraries ====
 * Added wikimedia/object-factory 1.0.0
-* …
 
 ==== Removed and replaced external libraries ====
 * (T17845) The deprecated 'jquery.badge' module was removed.
@@ -133,33 +125,35 @@ production.
 * The deprecated 'jquery.placeholder' module was removed.
 * The deprecated 'jquery.appear' module was removed. Use the
   'mediawiki.viewport' module instead.
-* The deprecated 'mediawiki.widgets.CategorySelector' module alias was removed.
-  Use the 'mediawiki.widgets.CategoryMultiselectWidget' module directly instead.
 * mediawiki/at-ease was replaced with wikimedia/at-ease.
 
 === Bug fixes in 1.31 ===
 * (T90902) Non-breaking space in header ID breaks anchor.
-* (T189375) CSSMin now allows quoted urls in `url()` syntax to start with a space.
+* (T189375) CSSMin now allows quoted urls in `url()` syntax to start with a
+  space.
+* (T2087, T10897, T87753, T174639) Whitespace created by category and language
+  links is now stripped rather than leaving blank lines in odd places.
+* (T3780) Uploads with UTF-8 names now work on PHP7.1+ on Windows servers.
 
 === Action API changes in 1.31 ===
 * (T185058) The 'name' value to tgprop for action=query&list=tags has been
   removed. It has never made a difference in the output, the name was always
   returned regardless.
-* The 'watch' and 'unwatch' parameters for action=move have been removed.  They
-  were deprecated and also accidentally nonfunctional since 1.17 in 2010.  Use
+* The 'watch' and 'unwatch' parameters for action=move have been removed. They
+  were deprecated and also accidentally nonfunctional since 1.17 in 2010. Use
   'watchlist' instead.
 
 === Action API internal changes in 1.31 ===
-* ApiBase::getProfileDBTime was removed (deprecated since 1.25)
-* ApiBase::getModuleProfileName was removed (deprecated since 1.25)
-* ApiBase::getProfileTime was removed (deprecated since 1.25)
+* ApiBase::getProfileDBTime, deprecated since 1.25, was removed.
+* ApiBase::getModuleProfileName, deprecated since 1.25, was removed.
+* ApiBase::getProfileTime, deprecated since 1.25, was removed.
 
 === Languages updated in 1.31 ===
 MediaWiki supports over 350 languages. Many localisations are updated
 regularly. Below only new and removed languages are listed, as well as
 changes to languages because of Phabricator reports.
 
-* (T180052) Mirandese (mwl) now supports gendered NS_USER/NS_USER_TALK namespaces.
+* (T180052) Mirandese (mwl) now supports gendered NS_USER/NS_USER_TALK.
 * (T182305) New language support: Nyungar (nys).
 * (T186359) New language support: Siberian Tatar [cебертатар] (sty).
 * (T186635) New language support: Guianan Creole (gcr).
@@ -169,17 +163,16 @@ changes to languages because of Phabricator reports.
 * (T189127) New language support: Gorontalo (gor).
 
 === Breaking changes in 1.31 ===
-* MessageBlobStore::insertMessageBlob() (deprecated in 1.27) was removed.
-* The OutputPage class constructor now requires a context parameter,
-  (instantiating without context was deprecated in 1.18)
-* The mw.page JavaScript singleton (deprecated in 1.30) was removed.
+* MessageBlobStore::insertMessageBlob(), deprecated in 1.27, was removed.
+* The OutputPage class constructor now requires a context parameter.
+  Instantiating without context was deprecated in 1.18.
+* The mw.page JavaScript singleton, deprecated in 1.30, was removed.
 * Article::getLastPurgeTimestamp(), WikiPage::getLastPurgeTimestamp(), and the
   related WikiPage::PURGE_* constants, deprecated in 1.29, were removed.
-* The Article::selectFields(), Article::onArticleCreate(),
-  Article::onArticleDelete(), and Article::onArticleEdit() methods, deprecated
-  in 1.24, were removed.
-* Installer::locateExecutable() and Installer::locateExecutableInDefaultPaths()
-  were removed, use ExecutableFinder::findInDefaultPaths() instead.
+* The Article::selectFields(), ::onArticleCreate(), ::onArticleDelete(), and
+  ::onArticleEdit() methods, deprecated in 1.24, were removed.
+* Installer::locateExecutable() and ::locateExecutableInDefaultPaths() were
+  removed. Use ExecutableFinder::findInDefaultPaths() instead.
 * The deprecated MW_DIFF_VERSION constant was removed.
   DifferenceEngine::MW_DIFF_VERSION should be used instead.
 * Due to significant refactoring, method ContribsPager::getUserCond() that had
@@ -187,8 +180,8 @@ changes to languages because of Phabricator reports.
 * The Block class will no longer accept usable-but-missing usernames for
   'byText' or ->setBlocker(). Callers should either ensure the blocker exists
   locally or use a new interwiki-format username like "iw>Example".
-* The following methods and constants from the WatchedItem class, which were deprecated in
-  1.27, have been removed.
+* The following methods and constants from the WatchedItem class, which were
+  deprecated in 1.27, have been removed:
   * WatchedItem::getTitle()
   * WatchedItem::fromUserTitle()
   * WatchedItem::addWatch()
@@ -199,22 +192,24 @@ changes to languages because of Phabricator reports.
   * WatchedItem::CHECK_USER_RIGHTS
   * WatchedItem::DEPRECATED_USAGE_TIMESTAMP
 * The $statementsOnOwnLine parameter of JavaScriptMinifier::minify was removed.
-  The corresponding configuration variable ($wgResourceLoaderMinifierStatementsOnOwnLine)
-  has been deprecated since 1.27 and was removed as well.
+  $wgResourceLoaderMinifierStatementsOnOwnLine, the corresponding configuration
+  variable, has been deprecated since 1.27 and was removed as well.
 * The $maxLineLength parameter of JavaScriptMinifier::minify was removed.
-  The corresponding configuration variable ($wgResourceLoaderMinifierMaxLineLength)
-  has been deprecated since 1.27 and was removed as well.
-* The HtmlFormatter class was removed (deprecated in 1.27). The namespaced
+  $wgResourceLoaderMinifierMaxLineLength, the corresponding configuration
+  variable, has been deprecated since 1.27 and was removed as well.
+* The HtmlFormatter class, deprecated in 1.27, was removed. The namespaced
   HtmlFormatter\HtmlFormatter class should be used instead.
 * The driver 'mysql' for MySQL, deprecated in MediaWiki 1.30, has been removed.
   The driver has been deprecated since PHP 5.5 and was removed in PHP 7.0. The
   default driver for MySQL has been 'mysqli' since MediaWiki 1.22.
-* The following properties of PreparedEdit were deprecated in 1.21 and have been removed:
+* The following properties of PreparedEdit were deprecated in 1.21 and have
+  been removed:
   * PreparedEdit->newText
   * PreparedEdit->oldText
   * PreparedEdit->pst
-* ParserOutput objects generated using a non-default value for
-  ParserOptions::setWrapOutputClass() can no longer be added to the parser cache.
+* ParserOutput objects which are generated using a non-default value for
+  ParserOptions::setWrapOutputClass() can no longer be added to the parser
+  cache.
 * The following deprecated methods from the OutputPage class have been removed:
   * OutputPage::addExtensionStyle(); deprecated in 1.27
   * OutputPage::getExtStyle(); deprecated in 1.27
@@ -222,69 +217,78 @@ changes to languages because of Phabricator reports.
   * OutputPage::setSquidMaxage(); deprecated in 1.27
   * OutputPage::readOnlyPage(); deprecated in 1.25
   * OutputPage::rateLimited(); deprecated in 1.25
-  * Additionally, the protected OutputPage::$mExtStyles array, only accessed through
-    the above and with no known uses, was removed.
+  * Additionally, the protected OutputPage::$mExtStyles array, only accessed
+    through the above and with no known uses, was removed.
 * The no-op method Skin::showIPinHeader(), deprecated in 1.27, was removed.
-* The following variables and methods in EditPage, deprecated in MediaWiki 1.30, were removed:
+* The following variables and methods in EditPage, deprecated in MediaWiki 1.30,
+  were removed:
   * $isCssJsSubpage — use ::isUserConfigPage()
   * $isCssSubpage — use ::isUserCssConfigPage()
   * $isJsSubpage — use ::isUserJsConfigPage()
-  * $isWrongCaseCssJsPage – use ::isWrongCaseUserConfigPage()
-  * ::getSummaryInput() – use ::getSummaryInputWidget()
-  * ::getSummaryInputOOUI() – use ::getSummaryInputWidget()
-  * ::getCheckboxes() – use ::getCheckboxesWidget() or ::getCheckboxesDefinition()
-  * ::getCheckboxesOOUI() – use ::getCheckboxesWidget() or ::getCheckboxesDefinition()
-* The method ResourceLoaderModule::getPosition(), deprecated in 1.29, has been removed.
-* In User, the cookie-related methods which were wrappers for the functions on the response
-  object, and were deprecated in 1.27, have been removed:
+  * $isWrongCaseCssJsPage – use ::isWrongCaseUserConfigPage()
+  * ::getSummaryInput() – use ::getSummaryInputWidget()
+  * ::getSummaryInputOOUI() – use ::getSummaryInputWidget()
+  * ::getCheckboxes() – use ::getCheckboxesWidget() or
+      ::getCheckboxesDefinition()
+  * ::getCheckboxesOOUI() – use ::getCheckboxesWidget() or
+      ::getCheckboxesDefinition()
+* ResourceLoaderModule::getPosition(), deprecated in 1.29, has been removed.
+* In User, the cookie-related methods which were wrappers for the functions on
+  the response object, and were deprecated in 1.27, have been removed:
   * ::setCookie()
   * ::clearCookie()
   * ::setExtendedLoginCookie()
   Note that User::setCookies() remains, and is not deprecated.
-* Also in User, some auth-related methods which were deprecated in 1.27, have been removed:
-  * ::getEditTokenTimestamp() – use MediaWiki\Session\Token::getTimestamp()
-  * ::getPasswordFactory() – create a PasswordFactory directly
+* Also in User, some auth-related methods which were deprecated in 1.27 have
+  been removed:
+  * ::getEditTokenTimestamp() – use MediaWiki\Session\Token::getTimestamp()
+  * ::getPasswordFactory() – create a PasswordFactory directly
   * ::passwordChangeInputAttribs()
-* The global functions wfProfileIn and wfProfileOut, deprecated in 1.25, have been removed.
+* The global functions wfProfileIn and wfProfileOut, deprecated in 1.25, have
+  been removed.
 * SpecialPageFactory::getList(), deprecated in 1.24, has been removed. You can
   use ::getNames() instead.
 * OpenSearch::getOpenSearchTemplate(), deprecated in 1.25, has been removed. You
   can use ApiOpenSearch::getOpenSearchTemplate() instead.
 * The global function wfBaseConvert, deprecated in 1.27, has been removed. Use
   Wikimedia\base_convert() directly.
-* Calling Database::begin() explicitly during an implicit transaction or when DBO_TRX
-  is set results in an exception. Calling Database::commit() explicitly for an implicit
-  transaction also results in an exception. Previously these were logged as errors.
-  The startAtomic() and endAtomic() methods, or AtomicSectionUpdate should be used
-  instead.
+* Calling Database::begin() explicitly during an implicit transaction or when
+  DBO_TRX is set results in an exception. Calling Database::commit() explicitly
+  for an implicit transaction also results in an exception. Previously these
+  were logged as errors. The startAtomic() and endAtomic() methods, or
+  AtomicSectionUpdate should be used instead.
 * The global function wfOutputHandler() was removed, use the its replacement
-  MediaWiki\OutputHandler::handle() instead. The global function was only sometimes defined.
-  Its replacement is always available via the autoloader.
-* ChangeTags::listExtensionActivatedTags and ::listExtensionDefinedTags, deprecated
-  in 1.28, have been removed.  Use ::listSoftwareActivatedTags() and
+  MediaWiki\OutputHandler::handle() instead. The global function was only
+  sometimes defined. Its replacement is always available via the autoloader.
+* ChangeTags::listExtensionActivatedTags and ::listExtensionDefinedTags,
+  deprecated in 1.28, have been removed. Use ::listSoftwareActivatedTags() and
   ::listSoftwareDefinedTags() instead.
-* Title::getTitleInvalidRegex(), deprecated in 1.25, has been removed. You
-  can use MediaWikiTitleCodec::getTitleInvalidRegex() instead.
+* Title::getTitleInvalidRegex(), deprecated in 1.25, has been removed. You can
+  use MediaWikiTitleCodec::getTitleInvalidRegex() instead.
 * HTMLForm & VFormHTMLForm::isVForm(), deprecated in 1.25, have been removed.
 * The ProfileSection class, deprecated in 1.25 and unused, has been removed.
-* The ResourceLoaderGetLessVars hook, deprecated in 1.30, has been removed.
-  Use ResourceLoaderModule::getLessVars() to expose local variables instead
-  of global ones.
-* As part of work to modernise user-generated content clean-up, a config option and some
-  methods related to HTML validity were removed without deprecation. The public methods
-  MWTidy::checkErrors() and its callee TidyDriverBase::validate() are removed, as are
-  MediaWikiTestCase::assertValidHtmlSnippet() and ::assertValidHtmlDocument(). The
-  $wgValidateAllHtml configuration option is removed and will be ignored.
-* Execution of external programs using MediaWiki\Shell\Command now applies RESTRICT_DEFAULT
-  Firejail restriction by default.
+* The ResourceLoaderGetLessVars hook, deprecated in 1.30, has been removed. Use
+  ResourceLoaderModule::getLessVars() to expose local variables instead of
+  global ones.
+* As part of work to modernise user-generated content clean-up, a config option
+  and some methods related to HTML validity were removed without deprecation.
+  The public methods MWTidy::checkErrors() and the path through which it was
+  called, TidyDriverBase::validate(), are removed, as are the testing methods
+  MediaWikiTestCase::assertValidHtmlSnippet() and ::assertValidHtmlDocument().
+  The $wgValidateAllHtml configuration option is removed and will be ignored.
+* Execution of external programs using MediaWiki\Shell\Command now applies
+  the RESTRICT_DEFAULT Firejail restriction by default.
 * The ResourceLoaderModule::getHashMtime() and ::getDefinitionMtime() methods,
   deprecated in 1.26, were removed.
+* The deprecated 'mediawiki.widgets.CategorySelector' module alias was removed.
+  Use the 'mediawiki.widgets.CategoryMultiselectWidget' module directly.
 
 === Deprecations in 1.31 ===
 * The Revision class was deprecated in favor of RevisionStore, BlobStore, and
   RevisionRecord and its subclasses.
 * The global function wfBCP47 is deprecated in favour of LanguageCode::bcp47.
-* The global function wfCountDown is now deprecated in favor of Maintenance::countDown.
+* The global function wfCountDown is now deprecated in favor of
+  Maintenance::countDown.
 * Several methods for returning lists of fields to select from the database
   have been deprecated in favor of similar methods that also return the tables
   to select from and the join conditions for those tables.
@@ -313,9 +317,9 @@ changes to languages because of Phabricator reports.
 * Use of Maintenance::error( $err, $die ) to exit script was deprecated. Use
   Maintenance::fatalError() instead.
 * Passing a ParserOptions object to OutputPage::parserOptions() is deprecated.
-* The RevisionInsertComplete hook is now deprecated, use RevisionRecordInserted instead.
-  RevisionInsertComplete is still called, but the second and third parameter will always be null.
-  Hard deprecation is scheduled for 1.32.
+* The RevisionInsertComplete hook is now deprecated; use instead the hook
+  RevisionRecordInserted. RevisionInsertComplete is still called, but the second
+  and third parameter will always be null. Hard deprecation is scheduled for 1.32.
 * The following methods that get and set ParserOutput state are deprecated.
   Callers should use the new stateless $options parameter to
   ParserOutput::getText() instead.
@@ -327,32 +331,39 @@ changes to languages because of Phabricator reports.
   * ParserOutput::setTOCEnabled()
   * OutputPage::enableSectionEditLinks()
   * OutputPage::sectionEditLinksEnabled()
-  * The public ParserOutput state fields $mTOCEnabled and $mEditSectionTokens are also deprecated.
+  * The public ParserOutput state fields $mTOCEnabled and $mEditSectionTokens
+    are also deprecated.
 * License::getLicenses has been deprecated; use License::getLines instead.
 * QuickTemplate::setRef() was deprecated in favour of QuickTemplate::set().
-  Setting template variables by reference allowed violating the principle of data being
-  immutable once added to the skin template. In practice, this method was not being
-  used for that. Rather, setRef() existed as memory optimisation for PHP 4.
-* QuickTemplate::setTranslator() was deprecated in favour of Skin::msg() parameters.
-* MediaWikiI18N::set() was deprecated in favour of Skin::msg() parameters.
-* MediaWikiI18N::translate() was deprecated in favour of Skin::msg() or wfMessage().
+  Setting template variables by reference allowed violating the principle of
+  data being immutable once added to the skin template. In practice, this method
+  was not being used for that. Rather, setRef() existed as memory optimisation
+  for PHP 4.
+* QuickTemplate::setTranslator() and MediaWikiI18N::set() were deprecated in
+  favour of Skin::msg() parameters.
+* MediaWikiI18N::translate() was deprecated in favour of Skin::msg() or
+  wfMessage().
 * Passing false to ParserOptions::setWrapOutputClass() is deprecated. Use the
   'unwrap' transform to ParserOutput::getText() instead.
-* \ObjectFactory (no namespace) is deprecated, the namespaced \Wikimedia\ObjectFactory
-  from the wikimedia/object-factory library should be used instead.
-* CommentStore::newKey is deprecated. Get an instance from MediaWikiServices instead.
-* The following CommentStore methods have had their signatures changed to introduce a $key parameter,
-  usage of the methods on instances retrieved from CommentStore::newKey will remain unchanged but deprecated:
+* \ObjectFactory (no namespace) is deprecated, the namespaced class
+  \Wikimedia\ObjectFactory from the wikimedia/object-factory library should be
+  used instead.
+* CommentStore::newKey is deprecated. Instead, get an instance from
+  MediaWikiServices.
+* The following CommentStore methods have had their signatures changed to
+  introduce a $key parameter, usage of the methods on instances retrieved from
+  CommentStore::newKey will remain unchanged but deprecated:
   * CommentStore::getFields
   * CommentStore::getJoin
   * CommentStore::getComment
   * CommentStore::getCommentLegacy
   * CommentStore::insert
   * CommentStore::insertWithTemplate
-* The following methods in Title have been renamed, and the old ones are deprecated:
-  * Title::getSkinFromCssJsSubpage – use ::getSkinFromConfigSubpage
-  * Title::isCssOrJsPage – use ::isSiteConfigPage
-  * Title::isCssJsSubpage – use ::isUserConfigPage
+* The following methods in Title have been renamed, and the old ones are
+  deprecated:
+  * Title::getSkinFromCssJsSubpage – use ::getSkinFromConfigSubpage
+  * Title::isCssOrJsPage – use ::isSiteConfigPage
+  * Title::isCssJsSubpage – use ::isUserConfigPage
   * Title::isCssSubpage – use ::isUserCssConfigPage
   * Title::isJsSubpage – use ::isUserJsConfigPage
 * The following methods related to caching of half-parsed HTML were deprecated:
@@ -369,22 +380,23 @@ changes to languages because of Phabricator reports.
   used instead.
 * The function wfShellWikiCmd() has been deprecated, use
   MediaWiki\Shell::makeScriptCommand().
-
 === Other changes in 1.31 ===
 * Browser support for Internet Explorer 10 was lowered from Grade A to Grade C.
-* Browser support for Opera 12 and older was removed. Opera 15+ continues at Grade A.
-* Introducing multi-content-revision capability into the storage layer. For details,
-  see <https://www.mediawiki.org/wiki/Requests_for_comment/Multi-Content_Revisions>.
-* The "free" CSS class is now only applied to unbracketed URLs in wikitext. Links
-  written using square brackets will get the class "text" not "free".
+* Browser support for Opera 12 and older was dropped entirely. Opera 15+
+  continues at Grade A.
+* Multi-content-revision capability was introduced into the storage layer. See
+  <https://mediawiki.org/wiki/Requests_for_comment/Multi-Content_Revisions>.
+* The "free" CSS class is now only applied to unbracketed URLs in wikitext.
+  Links written using square brackets will get the class "text" not "free".
 * RFC 157418: Whitespace is trimmed from wikitext headings, wikitext list items,
   wikitext table captions, wikitext table headings, wikitext table cells. HTML
-  headings, HTML list items, HTML table captions, HTML table headings, HTML table cells
-  will not have this trimming behavior.
+  headings, HTML list items, HTML table captions, HTML table headings, HTML
+  table cells will not have this trimming behavior.
 
 == Compatibility ==
-MediaWiki 1.31 requires PHP 5.5.9 or later. Although HHVM 3.18.5 or later is supported,
-it is generally advised to use PHP 5.5.9 or later for long term support.
+MediaWiki 1.31 requires PHP 7.0.0 or later. Although HHVM 3.18.5 or later is
+supported, it is generally advised to use PHP 7.0.0 or later for long term
+support.
 
 MySQL/MariaDB is the recommended DBMS. PostgreSQL or SQLite can also be used,
 but support for them is somewhat less mature. There is experimental support for
index a1edddb..9fd3161 100644 (file)
@@ -6,12 +6,17 @@ MediaWiki 1.32 is an alpha-quality branch and is not recommended for use in
 production.
 
 === Configuration changes in 1.32 ===
-* (T115414) The $wgEnableAPI and $wgEnableWriteAPI settings, deprecated in 1.31, have been removed.
+* (T115414) The $wgEnableAPI and $wgEnableWriteAPI settings, deprecated in 1.31,
+  have been removed.
 * The $wgUseAjax setting, deprecated in 1.31, is now ignored.
 * The $wgSiteSupportPage setting, unused since 1.5, was removed.
-* $wgJpegQuality was added to allow configuring the quality of JPEG thumbnails (default 80).
-* The default quality of JPEG thumbnails generated by GD was reduced from 95 to 80.
-* …
+* The default quality of JPEG thumbnails generated by GD was reduced from 95 to
+  80. The quality of JPEG thumbnails is now configurable through the new setting
+  $wgJpegQuality (default 80). This aligns the quality to what ImageMagick uses.
+* $wgExperimentalHtmlIds, deprecated since 1.30, has been removed. The
+  'html5-legacy' value for $wgFragmentMode is no longer accepted.
+* The experimental Html5Internal and Html5Depurate tidy drivers were removed.
+  RemexHtml, which is the default, should be used instead.
 
 === New features in 1.32 ===
 * (T112474) Generalized the ResourceLoader mechanism for overriding modules
@@ -22,7 +27,7 @@ production.
 * …
 
 ==== Upgraded external libraries ====
-* 
+* Updated QUnit from 2.4.0 to 2.6.0.
 
 ==== New external libraries ====
 * …
@@ -40,37 +45,61 @@ production.
 * Added 'ApiParseMakeOutputPage' hook.
 
 === Languages updated in 1.32 ===
-MediaWiki supports over 350 languages. Many localisations are updated
-regularly. Below only new and removed languages are listed, as well as
-changes to languages because of Phabricator reports.
+MediaWiki supports over 350 languages. Many localisations are updated regularly.
+Below only new and removed languages are listed, as well as changes to languages
+because of Phabricator reports.
 
-* 
+* (T193566) Added language support for Ambonese Malay (abs).
 
 === Breaking changes in 1.32 ===
-* $wgRequestTime was removed (deprecated in 1.25).
-  Use $_SERVER['REQUEST_TIME_FLOAT'] or WebRequest::getElapsedTime() instead.
-* The MediaWikiI18N class was removed (deprecated in 1.31).
-* QuickTemplate::setTranslator() was removed (deprecated in 1.31).
-  Use Skin::msg() instead.
-* wfInitShellLocale() was removed (deprecated in 1.30).
-* wfShellExecDisabled() was removed (deprecated in 1.30).
-* The type string for the parameter $lang of DateFormatter::getInstance is
-  removed (deprecated in 1.31).
-* The EDIT_TOKEN_SUFFIX constant was removed (deprecated in 1.27).
-  Use MediaWiki\Session\Token::SUFFIX instead.
+* $wgRequestTime, deprecated in 1.25, was removed. Use
+  $_SERVER['REQUEST_TIME_FLOAT'] or WebRequest::getElapsedTime() instead.
+* The MediaWikiI18N class, deprecated in 1.31, was removed.
+* QuickTemplate::setTranslator(), deprecated in 1.31, was removed. Use
+  Skin::msg() instead.
+* wfInitShellLocale(), deprecated in 1.30, was removed.
+* wfShellExecDisabled(), deprecated in 1.30, was removed.
+* The type string for the parameter $lang of DateFormatter::getInstance,
+  deprecated in 1.31, was removed.
+* The EDIT_TOKEN_SUFFIX constant deprecated in 1.27, was removed. Use
+  MediaWiki\Session\Token::SUFFIX instead.
+* EditPage::isOouiEnabled() deprecated in 1.30, was removed.
+* mw.util.wikiGetlink(), deprecated in 1.23, was removed. Use mw.util.getUrl()
+  instead.
+* (T61113) The following methods and constants from the Revision class, which
+  were deprecated in 1.25, have now been removed:
+  * Revision::getRawUser()
+  * Revision::getRawUserText()
+  * Revision::getRawComment()
+* window.gM() from mediawiki.jqueryMsg, deprecated in 1.23, was removed. Use
+  mw.msg() or mw.message() instead.
+* mw.util.escapeId(), deprecated in 1.30, was removed. Use
+  mw.util.escapeIdForAttribute or mw.util.escapeIdForLink instead.
+* mw.util.updateTooltipAccessKeys(), deprecated in 1.24, was removed. Use
+  jquery.accessKeyLabel instead.
+* The SqlDataUpdate class, deprecated in 1.28, has been removed.
+* The Html5Internal and Html5Depurate tidy driver classes were removed, along with the
+  Balancer tidy implementation. Both implementations were experimental, and were replaced
+  by RemexHtml.
 
 === Deprecations in 1.32 ===
 * Use of a StartProfiler.php file is deprecated in favour of placing
   configuration in LocalSettings.php.
 * HTMLForm::setSubmitProgressive() is deprecated. No need to call it. Submit
   button is already marked as progressive.
+* Skin::setupSkinUserCss() is deprecated. Adding of modules to load
+  has been centralised to Skin::getDefaultModules(), which is now capable
+  of queueing style modules as well.
+* OutputPage::addModuleScripts() and ParserOutput::addModuleScripts are
+  deprecated. Use addModules() instead.
 
 === Other changes in 1.32 ===
 * …
 
 == Compatibility ==
-MediaWiki 1.32 requires PHP 5.5.9 or later. Although HHVM 3.18.5 or later is supported,
-it is generally advised to use PHP 5.5.9 or later for long term support.
+MediaWiki 1.32 requires PHP 5.5.9 or later. Although HHVM 3.18.5 or later is
+supported, it is generally advised to use PHP 5.5.9 or later for long term
+support.
 
 MySQL/MariaDB is the recommended DBMS. PostgreSQL or SQLite can also be used,
 but support for them is somewhat less mature. There is experimental support for
index ece4661..ec0d59f 100644 (file)
@@ -201,15 +201,15 @@ $wgAutoloadLocalClasses = [
        'BenchmarkSanitizer' => __DIR__ . '/maintenance/benchmarks/benchmarkSanitizer.php',
        'BenchmarkTidy' => __DIR__ . '/maintenance/benchmarks/benchmarkTidy.php',
        'Benchmarker' => __DIR__ . '/maintenance/benchmarks/Benchmarker.php',
-       'BitmapHandler' => __DIR__ . '/includes/media/Bitmap.php',
-       'BitmapHandler_ClientOnly' => __DIR__ . '/includes/media/Bitmap_ClientOnly.php',
+       'BitmapHandler' => __DIR__ . '/includes/media/BitmapHandler.php',
+       'BitmapHandler_ClientOnly' => __DIR__ . '/includes/media/BitmapHandler_ClientOnly.php',
        'BitmapMetadataHandler' => __DIR__ . '/includes/media/BitmapMetadataHandler.php',
        'Blob' => __DIR__ . '/includes/libs/rdbms/encasing/Blob.php',
        'Block' => __DIR__ . '/includes/Block.php',
        'BlockLevelPass' => __DIR__ . '/includes/parser/BlockLevelPass.php',
        'BlockListPager' => __DIR__ . '/includes/specials/pagers/BlockListPager.php',
        'BlockLogFormatter' => __DIR__ . '/includes/logging/BlockLogFormatter.php',
-       'BmpHandler' => __DIR__ . '/includes/media/BMP.php',
+       'BmpHandler' => __DIR__ . '/includes/media/BmpHandler.php',
        'BotPassword' => __DIR__ . '/includes/user/BotPassword.php',
        'BrokenRedirectsPage' => __DIR__ . '/includes/specials/SpecialBrokenRedirects.php',
        'BufferingStatsdDataFactory' => __DIR__ . '/includes/libs/stats/BufferingStatsdDataFactory.php',
@@ -396,7 +396,7 @@ $wgAutoloadLocalClasses = [
        'DiffOpDelete' => __DIR__ . '/includes/diff/DairikiDiff.php',
        'DifferenceEngine' => __DIR__ . '/includes/diff/DifferenceEngine.php',
        'Digit2Html' => __DIR__ . '/maintenance/language/digit2html.php',
-       'DjVuHandler' => __DIR__ . '/includes/media/DjVu.php',
+       'DjVuHandler' => __DIR__ . '/includes/media/DjVuHandler.php',
        'DjVuImage' => __DIR__ . '/includes/media/DjVuImage.php',
        'DnsSrvDiscoverer' => __DIR__ . '/includes/libs/DnsSrvDiscoverer.php',
        'DoubleRedirectJob' => __DIR__ . '/includes/jobqueue/jobs/DoubleRedirectJob.php',
@@ -452,7 +452,7 @@ $wgAutoloadLocalClasses = [
        'EventRelayerNull' => __DIR__ . '/includes/libs/eventrelayer/EventRelayerNull.php',
        'ExecutableFinder' => __DIR__ . '/includes/utils/ExecutableFinder.php',
        'Exif' => __DIR__ . '/includes/media/Exif.php',
-       'ExifBitmapHandler' => __DIR__ . '/includes/media/ExifBitmap.php',
+       'ExifBitmapHandler' => __DIR__ . '/includes/media/ExifBitmapHandler.php',
        'ExplodeIterator' => __DIR__ . '/includes/libs/ExplodeIterator.php',
        'ExportProgressFilter' => __DIR__ . '/includes/export/ExportProgressFilter.php',
        'ExportSites' => __DIR__ . '/maintenance/exportSites.php',
@@ -537,7 +537,7 @@ $wgAutoloadLocalClasses = [
        'FormatMetadata' => __DIR__ . '/includes/media/FormatMetadata.php',
        'FormattedRCFeed' => __DIR__ . '/includes/rcfeed/FormattedRCFeed.php',
        'FormlessAction' => __DIR__ . '/includes/actions/FormlessAction.php',
-       'GIFHandler' => __DIR__ . '/includes/media/GIF.php',
+       'GIFHandler' => __DIR__ . '/includes/media/GIFHandler.php',
        'GIFMetadataExtractor' => __DIR__ . '/includes/media/GIFMetadataExtractor.php',
        'GanConverter' => __DIR__ . '/languages/classes/LanguageGan.php',
        'GenderCache' => __DIR__ . '/includes/cache/GenderCache.php',
@@ -699,7 +699,7 @@ $wgAutoloadLocalClasses = [
        'JobQueueSecondTestQueue' => __DIR__ . '/includes/jobqueue/JobQueueSecondTestQueue.php',
        'JobRunner' => __DIR__ . '/includes/jobqueue/JobRunner.php',
        'JobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php',
-       'JpegHandler' => __DIR__ . '/includes/media/Jpeg.php',
+       'JpegHandler' => __DIR__ . '/includes/media/JpegHandler.php',
        'JpegMetadataExtractor' => __DIR__ . '/includes/media/JpegMetadataExtractor.php',
        'JsonContent' => __DIR__ . '/includes/content/JsonContent.php',
        'JsonContentHandler' => __DIR__ . '/includes/content/JsonContentHandler.php',
@@ -971,14 +971,6 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Storage\\SlotRecord' => __DIR__ . '/includes/Storage/SlotRecord.php',
        'MediaWiki\\Storage\\SqlBlobStore' => __DIR__ . '/includes/Storage/SqlBlobStore.php',
        'MediaWiki\\Storage\\SuppressedDataException' => __DIR__ . '/includes/Storage/SuppressedDataException.php',
-       'MediaWiki\\Tidy\\BalanceActiveFormattingElements' => __DIR__ . '/includes/tidy/Balancer.php',
-       'MediaWiki\\Tidy\\BalanceElement' => __DIR__ . '/includes/tidy/Balancer.php',
-       'MediaWiki\\Tidy\\BalanceMarker' => __DIR__ . '/includes/tidy/Balancer.php',
-       'MediaWiki\\Tidy\\BalanceSets' => __DIR__ . '/includes/tidy/Balancer.php',
-       'MediaWiki\\Tidy\\BalanceStack' => __DIR__ . '/includes/tidy/Balancer.php',
-       'MediaWiki\\Tidy\\Balancer' => __DIR__ . '/includes/tidy/Balancer.php',
-       'MediaWiki\\Tidy\\Html5Depurate' => __DIR__ . '/includes/tidy/Html5Depurate.php',
-       'MediaWiki\\Tidy\\Html5Internal' => __DIR__ . '/includes/tidy/Html5Internal.php',
        'MediaWiki\\Tidy\\RaggettBase' => __DIR__ . '/includes/tidy/RaggettBase.php',
        'MediaWiki\\Tidy\\RaggettExternal' => __DIR__ . '/includes/tidy/RaggettExternal.php',
        'MediaWiki\\Tidy\\RaggettInternalHHVM' => __DIR__ . '/includes/tidy/RaggettInternalHHVM.php',
@@ -1099,7 +1091,7 @@ $wgAutoloadLocalClasses = [
        'Orphans' => __DIR__ . '/maintenance/orphans.php',
        'OutputPage' => __DIR__ . '/includes/OutputPage.php',
        'PHPVersionCheck' => __DIR__ . '/includes/PHPVersionCheck.php',
-       'PNGHandler' => __DIR__ . '/includes/media/PNG.php',
+       'PNGHandler' => __DIR__ . '/includes/media/PNGHandler.php',
        'PNGMetadataExtractor' => __DIR__ . '/includes/media/PNGMetadataExtractor.php',
        'PPCustomFrame_DOM' => __DIR__ . '/includes/parser/Preprocessor_DOM.php',
        'PPCustomFrame_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
@@ -1183,6 +1175,8 @@ $wgAutoloadLocalClasses = [
        'PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php',
        'Preferences' => __DIR__ . '/includes/Preferences.php',
        'PreferencesForm' => __DIR__ . '/includes/specials/forms/PreferencesForm.php',
+       'PreferencesFormLegacy' => __DIR__ . '/includes/specials/forms/PreferencesFormLegacy.php',
+       'PreferencesFormOOUI' => __DIR__ . '/includes/specials/forms/PreferencesFormOOUI.php',
        'PrefixSearch' => __DIR__ . '/includes/PrefixSearch.php',
        'PreprocessDump' => __DIR__ . '/maintenance/preprocessDump.php',
        'Preprocessor' => __DIR__ . '/includes/parser/Preprocessor.php',
@@ -1483,7 +1477,6 @@ $wgAutoloadLocalClasses = [
        'SpecialWatchlist' => __DIR__ . '/includes/specials/SpecialWatchlist.php',
        'SpecialWhatLinksHere' => __DIR__ . '/includes/specials/SpecialWhatlinkshere.php',
        'SqlBagOStuff' => __DIR__ . '/includes/objectcache/SqlBagOStuff.php',
-       'SqlDataUpdate' => __DIR__ . '/includes/deferred/SqlDataUpdate.php',
        'SqlSearchResultSet' => __DIR__ . '/includes/search/SqlSearchResultSet.php',
        'Sqlite' => __DIR__ . '/maintenance/sqlite.inc',
        'SqliteInstaller' => __DIR__ . '/includes/installer/SqliteInstaller.php',
@@ -1507,7 +1500,7 @@ $wgAutoloadLocalClasses = [
        'StubUserLang' => __DIR__ . '/includes/StubObject.php',
        'SubmitAction' => __DIR__ . '/includes/actions/SubmitAction.php',
        'SubpageImportTitleFactory' => __DIR__ . '/includes/title/SubpageImportTitleFactory.php',
-       'SvgHandler' => __DIR__ . '/includes/media/SVG.php',
+       'SvgHandler' => __DIR__ . '/includes/media/SvgHandler.php',
        'SwiftFileBackend' => __DIR__ . '/includes/libs/filebackend/SwiftFileBackend.php',
        'SwiftFileBackendDirList' => __DIR__ . '/includes/libs/filebackend/SwiftFileBackend.php',
        'SwiftFileBackendFileList' => __DIR__ . '/includes/libs/filebackend/SwiftFileBackend.php',
@@ -1533,7 +1526,7 @@ $wgAutoloadLocalClasses = [
        'ThumbnailImage' => __DIR__ . '/includes/media/MediaTransformOutput.php',
        'ThumbnailRenderJob' => __DIR__ . '/includes/jobqueue/jobs/ThumbnailRenderJob.php',
        'TidyUpBug37714' => __DIR__ . '/maintenance/tidyUpBug37714.php',
-       'TiffHandler' => __DIR__ . '/includes/media/Tiff.php',
+       'TiffHandler' => __DIR__ . '/includes/media/TiffHandler.php',
        'Timing' => __DIR__ . '/includes/libs/Timing.php',
        'Title' => __DIR__ . '/includes/Title.php',
        'TitleArray' => __DIR__ . '/includes/TitleArray.php',
@@ -1663,7 +1656,7 @@ $wgAutoloadLocalClasses = [
        'WebInstallerUpgrade' => __DIR__ . '/includes/installer/WebInstallerUpgrade.php',
        'WebInstallerUpgradeDoc' => __DIR__ . '/includes/installer/WebInstallerUpgradeDoc.php',
        'WebInstallerWelcome' => __DIR__ . '/includes/installer/WebInstallerWelcome.php',
-       'WebPHandler' => __DIR__ . '/includes/media/WebP.php',
+       'WebPHandler' => __DIR__ . '/includes/media/WebPHandler.php',
        'WebRequest' => __DIR__ . '/includes/WebRequest.php',
        'WebRequestUpload' => __DIR__ . '/includes/WebRequestUpload.php',
        'WebResponse' => __DIR__ . '/includes/WebResponse.php',
index 8a5c5dd..6e34ec2 100644 (file)
@@ -34,7 +34,7 @@
                "wikimedia/cdb": "1.4.1",
                "wikimedia/cldr-plural-rule-parser": "1.0.0",
                "wikimedia/composer-merge-plugin": "1.4.1",
-               "wikimedia/html-formatter": "1.0.1",
+               "wikimedia/html-formatter": "1.0.2",
                "wikimedia/ip-set": "1.2.0",
                "wikimedia/object-factory": "1.0.0",
                "wikimedia/php-session-serializer": "1.0.6",
index 91b7e77..6e88d68 100644 (file)
@@ -71,7 +71,7 @@ want to write code destined for Wikipedia.
 It's often the case that the best algorithm to use for a given task
 depends on whether or not replication is in use. Due to our unabashed
 Wikipedia-centrism, we often just use the replication-friendly version,
-but if you like, you can use wfGetLB()->getServerCount() > 1 to
+but if you like, you can use LoadBalancer::getServerCount() > 1 to
 check to see if replication is in use.
 
 === Lag ===
@@ -107,7 +107,7 @@ in the session, and then at the start of each request, waiting for the
 slave to catch up to that position before doing any reads from it. If
 this wait times out, reads are allowed anyway, but the request is
 considered to be in "lagged slave mode". Lagged slave mode can be
-checked by calling wfGetLB()->getLaggedReplicaMode(). The only
+checked by calling LoadBalancer::getLaggedReplicaMode(). The only
 practical consequence at present is a warning displayed in the page
 footer.
 
index 55f6857..e9b08e8 100644 (file)
@@ -134,7 +134,7 @@ class CommentStore {
        /**
         * Compat method allowing use of self::newKey until removed.
         * @param string|null $methodKey
-        * @throw InvalidArgumentException
+        * @throws InvalidArgumentException
         * @return string
         */
        private function getKey( $methodKey = null ) {
index 4f4fa86..0e98e33 100644 (file)
@@ -1919,8 +1919,8 @@ $wgSQLiteDataDir = '';
  * $wgSharedSchema is the table schema for the shared database. It defaults to
  * $wgDBmwschema.
  *
- * @deprecated since 1.21 In new code, use the $wiki parameter to wfGetLB() to
- *   access remote databases. Using wfGetLB() allows the shared database to
+ * @deprecated since 1.21 In new code, use the $wiki parameter to LBFactory::getMainLB() to
+ *   access remote databases. Using LBFactory::getMainLB() allows the shared database to
  *   reside on separate servers to the wiki's own database, with suitable
  *   configuration of $wgLBFactoryConf.
  */
@@ -3237,6 +3237,14 @@ $wgHTMLFormAllowTableFormat = true;
  */
 $wgUseMediaWikiUIEverywhere = false;
 
+/**
+ * Temporary variable that determines whether the EditPage class should use OOjs UI or not.
+ * This will be removed later and OOjs UI will become the only option.
+ *
+ * @since 1.32
+ */
+$wgOOUIPreferences = false;
+
 /**
  * Whether to label the store-to-database-and-show-to-others button in the editor
  * as "Save page"/"Save changes" if false (the default) or, if true, instead as
@@ -3372,23 +3380,12 @@ $wgApiFrameOptions = 'DENY';
  */
 $wgDisableOutputCompression = false;
 
-/**
- * Abandoned experiment with HTML5-style ID escaping. Normalized IDs a bit
- * too aggressively, breaking preexisting content (particularly Cite).
- * See T29733, T29694, T29474.
- *
- * @deprecated since 1.30, use $wgFragmentMode
- */
-$wgExperimentalHtmlIds = false;
-
 /**
  * How should section IDs be encoded?
  * This array can contain 1 or 2 elements, each of them can be one of:
  * - 'html5'  is modern HTML5 style encoding with minimal escaping. Displays Unicode
  *            characters in most browsers' address bars.
  * - 'legacy' is old MediaWiki-style encoding, e.g. 啤酒 turns into .E5.95.A4.E9.85.92
- * - 'html5-legacy' corresponds to DEPRECATED $wgExperimentalHtmlIds mode. DO NOT use
- *            it for anything but migration off that mode (see below).
  *
  * The first element of this array specifies the primary mode of escaping IDs. This
  * is what users will see when they e.g. follow an [[#internal link]] to a section of
@@ -4284,8 +4281,6 @@ $wgAllowImageTag = false;
  *    - RaggettInternalHHVM: Use the limited-functionality HHVM extension
  *    - RaggettInternalPHP: Use the PECL extension
  *    - RaggettExternal: Shell out to an external binary (tidyBin)
- *    - Html5Depurate: Use external Depurate service
- *    - Html5Internal: Use the Balancer library in PHP
  *    - RemexHtml: Use the RemexHtml library in PHP
  *
  *  - tidyConfigFile: Path to configuration file for any of the Raggett drivers
index fcf3d49..4f6b7b4 100644 (file)
@@ -504,16 +504,6 @@ class EditPage {
                }
        }
 
-       /**
-        * Check if the edit page is using OOUI controls
-        * @return bool Always true
-        * @deprecated since 1.30
-        */
-       public function isOouiEnabled() {
-               wfDeprecated( __METHOD__, '1.30' );
-               return true;
-       }
-
        /**
         * Returns if the given content model is editable.
         *
index 519b22c..9569bc1 100644 (file)
@@ -712,6 +712,8 @@ function wfAssembleUrl( $urlParts ) {
  *
  * @todo Need to integrate this into wfExpandUrl (see T34168)
  *
+ * @since 1.19
+ *
  * @param string $urlPath URL path, potentially containing dot-segments
  * @return string URL path with all dot-segments removed
  */
index 82ffcfb..e6dc0fe 100644 (file)
@@ -998,8 +998,14 @@ class MediaWiki {
         * @param LoggerInterface $runJobsLogger
         */
        private function triggerSyncJobs( $n, LoggerInterface $runJobsLogger ) {
-               $runner = new JobRunner( $runJobsLogger );
-               $runner->run( [ 'maxJobs' => $n ] );
+               $trxProfiler = Profiler::instance()->getTransactionProfiler();
+               $old = $trxProfiler->setSilenced( true );
+               try {
+                       $runner = new JobRunner( $runJobsLogger );
+                       $runner->run( [ 'maxJobs' => $n ] );
+               } finally {
+                       $trxProfiler->setSilenced( $old );
+               }
        }
 
        /**
index 56df0f0..fbc7b60 100644 (file)
@@ -550,9 +550,7 @@ class OutputPage extends ContextSource {
        }
 
        /**
-        * Add one or more modules recognized by ResourceLoader. Modules added
-        * through this function will be loaded by ResourceLoader when the
-        * page loads.
+        * Load one or more ResourceLoader modules on this page.
         *
         * @param string|array $modules Module name (string) or array of module names
         */
@@ -561,7 +559,7 @@ class OutputPage extends ContextSource {
        }
 
        /**
-        * Get the list of module JS to include on this page
+        * Get the list of script-only modules to load on this page.
         *
         * @param bool $filter
         * @param string|null $position Unused
@@ -574,10 +572,13 @@ class OutputPage extends ContextSource {
        }
 
        /**
-        * Add only JS of one or more modules recognized by ResourceLoader. Module
-        * scripts added through this function will be loaded by ResourceLoader when
-        * the page loads.
+        * Load the scripts of one or more ResourceLoader modules, on this page.
         *
+        * This method exists purely to provide the legacy behaviour of loading
+        * a module's scripts in the global scope, and without dependency resolution.
+        * See <https://phabricator.wikimedia.org/T188689>.
+        *
+        * @deprecated since 1.31 Use addModules() instead.
         * @param string|array $modules Module name (string) or array of module names
         */
        public function addModuleScripts( $modules ) {
@@ -585,7 +586,7 @@ class OutputPage extends ContextSource {
        }
 
        /**
-        * Get the list of module CSS to include on this page
+        * Get the list of style-only modules to load on this page.
         *
         * @param bool $filter
         * @param string|null $position Unused
@@ -598,11 +599,11 @@ class OutputPage extends ContextSource {
        }
 
        /**
-        * Add only CSS of one or more modules recognized by ResourceLoader.
+        * Load the styles of one or more ResourceLoader modules on this page.
         *
-        * Module styles added through this function will be added using standard link CSS
-        * tags, rather than as a combined Javascript and CSS package. Thus, they will
-        * load when JavaScript is disabled (unless CSS also happens to be disabled).
+        * Module styles added through this function will be loaded as a stylesheet,
+        * using a standard `<link rel=stylesheet>` HTML tag, rather than as a combined
+        * Javascript and CSS package. Thus, they will even load when JavaScript is disabled.
         *
         * @param string|array $modules Module name (string) or array of module names
         */
@@ -2331,6 +2332,23 @@ class OutputPage extends ContextSource {
                }
        }
 
+       /**
+        * Transfer styles and JavaScript modules from skin.
+        *
+        * @param Skin $sk to load modules for
+        */
+       public function loadSkinModules( $sk ) {
+               foreach ( $sk->getDefaultModules() as $group => $modules ) {
+                       if ( $group === 'styles' ) {
+                               foreach ( $modules as $key => $moduleMembers ) {
+                                       $this->addModuleStyles( $moduleMembers );
+                               }
+                       } else {
+                               $this->addModules( $modules );
+                       }
+               }
+       }
+
        /**
         * Finally, all the text has been munged and accumulated into
         * the object, let's actually output it:
@@ -2424,9 +2442,7 @@ class OutputPage extends ContextSource {
                        }
 
                        $sk = $this->getSkin();
-                       foreach ( $sk->getDefaultModules() as $group ) {
-                               $this->addModules( $group );
-                       }
+                       $this->loadSkinModules( $sk );
 
                        MWDebug::addModules( $this );
 
index 652ce4d..548ef8d 100644 (file)
@@ -786,17 +786,6 @@ class Revision implements IDBAccessObject {
                return $user ? $user->getId() : 0;
        }
 
-       /**
-        * Fetch revision's user id without regard for the current user's permissions
-        *
-        * @return int
-        * @deprecated since 1.25, use getUser( Revision::RAW )
-        */
-       public function getRawUser() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return $this->getUser( self::RAW );
-       }
-
        /**
         * Fetch revision's username if it's available to the specified audience.
         * If the specified audience does not have access to the username, an
@@ -820,18 +809,6 @@ class Revision implements IDBAccessObject {
                $user = $this->mRecord->getUser( $audience, $user );
                return $user ? $user->getName() : '';
        }
-
-       /**
-        * Fetch revision's username without regard for view restrictions
-        *
-        * @return string
-        * @deprecated since 1.25, use getUserText( Revision::RAW )
-        */
-       public function getRawUserText() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return $this->getUserText( self::RAW );
-       }
-
        /**
         * Fetch revision comment if it's available to the specified audience.
         * If the specified audience does not have access to the comment, an
@@ -856,17 +833,6 @@ class Revision implements IDBAccessObject {
                return $comment === null ? null : $comment->text;
        }
 
-       /**
-        * Fetch revision comment without regard for the current user's permissions
-        *
-        * @return string
-        * @deprecated since 1.25, use getComment( Revision::RAW )
-        */
-       public function getRawComment() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return $this->getComment( self::RAW );
-       }
-
        /**
         * @return bool
         */
index 6825c7b..5cc9a96 100644 (file)
@@ -361,12 +361,6 @@ foreach ( $wgForeignFileRepos as &$repo ) {
 }
 unset( $repo ); // no global pollution; destroy reference
 
-// Convert this deprecated setting to modern system
-if ( $wgExperimentalHtmlIds ) {
-       wfDeprecated( '$wgExperimentalHtmlIds', '1.30' );
-       $wgFragmentMode = [ 'html5-legacy', 'html5' ];
-}
-
 $rcMaxAgeDays = $wgRCMaxAge / ( 3600 * 24 );
 if ( $wgRCFilterByAge ) {
        // Trim down $wgRCLinkDays so that it only lists links which are valid
index 1329023..5b3daf4 100644 (file)
@@ -283,7 +283,7 @@ class RevisionStore
         * @param mixed $value
         * @param string $name
         *
-        * @throw IncompleteRevisionException if $value is null
+        * @throws IncompleteRevisionException if $value is null
         * @return mixed $value, if $value is not null
         */
        private function failOnNull( $value, $name ) {
@@ -300,7 +300,7 @@ class RevisionStore
         * @param mixed $value
         * @param string $name
         *
-        * @throw IncompleteRevisionException if $value is empty
+        * @throws IncompleteRevisionException if $value is empty
         * @return mixed $value, if $value is not null
         */
        private function failOnEmpty( $value, $name ) {
@@ -889,7 +889,7 @@ class RevisionStore
         * @param string|null $blobFormat MIME type indicating how $dataBlob is encoded
         * @param int $queryFlags
         *
-        * @throw RevisionAccessException
+        * @throws RevisionAccessException
         * @return Content
         */
        private function loadSlotContent(
index 70d7d42..72de2c9 100644 (file)
@@ -292,7 +292,7 @@ class SqlBlobStore implements IDBAccessObject, BlobStore {
         * @param string $blobAddress
         * @param int $queryFlags
         *
-        * @throw BlobAccessException
+        * @throws BlobAccessException
         * @return string|false
         */
        private function fetchBlob( $blobAddress, $queryFlags ) {
index e4c4429..0248f25 100644 (file)
@@ -130,7 +130,10 @@ class ApiLogin extends ApiBase {
                                $session = $status->getValue();
                                $authRes = 'Success';
                                $loginType = 'BotPassword';
-                       } elseif ( !$botLoginData[2] || $status->hasMessage( 'login-throttled' ) ) {
+                       } elseif ( !$botLoginData[2] ||
+                               $status->hasMessage( 'login-throttled' ) ||
+                               $status->hasMessage( 'botpasswords-needs-reset' )
+                       ) {
                                $authRes = 'Failed';
                                $message = $status->getMessage();
                                LoggerFactory::getInstance( 'authentication' )->info(
index 05b4289..096122d 100644 (file)
@@ -323,9 +323,7 @@ class ApiParse extends ApiBase {
                                // Based on OutputPage::headElement()
                                $skin->setupSkinUserCss( $outputPage );
                                // Based on OutputPage::output()
-                               foreach ( $skin->getDefaultModules() as $group ) {
-                                       $outputPage->addModules( $group );
-                               }
+                               $outputPage->loadSkinModules( $skin );
                        }
 
                        Hooks::run( 'ApiParseMakeOutputPage', [ $this, $outputPage ] );
index 0816ed7..5be4703 100644 (file)
        "apihelp-edit-param-tags": "Cambia las etiquetas para aplicarlas a la revisión.",
        "apihelp-edit-param-minor": "Edición menor.",
        "apihelp-edit-param-notminor": "Edición no menor.",
-       "apihelp-edit-param-bot": "Marcar esta edición como edición de bot.",
+       "apihelp-edit-param-bot": "Marcar esta como una edición de robot.",
        "apihelp-edit-param-basetimestamp": "Marca de tiempo de la revisión base, usada para detectar conflictos de edición. Se puede obtener mediante [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]]",
        "apihelp-edit-param-starttimestamp": "Marca de tiempo de cuando empezó el proceso de edición, usada para detectar conflictos de edición. Se puede obtener un valor apropiado usando <var>[[Special:ApiHelp/main|curtimestamp]]</var> cuando comiences el proceso de edición (por ejemplo, al cargar el contenido de la página por editar).",
        "apihelp-edit-param-recreate": "Reemplazar los errores acerca de la página de haber sido eliminados en el ínterin.",
index aaa6aed..8b1ca0f 100644 (file)
@@ -18,7 +18,7 @@
                        "Umherirrender"
                ]
        },
-       "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|ת×\99×¢×\95×\93]]\n* [[mw:Special:MyLanguage/API:FAQ|ש×\95\"ת]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api ×¨×©×\99×\9eת ×\93×\99×\95×\95ר]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce ×\94×\95×\93×¢×\95ת ×¢×\9c API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R ×\91×\90×\92×\99×\9d ×\95×\91קש×\95ת]\n</div>\n<strong>×\9eצ×\91:</strong> ×\9b×\9c ×\94×\90פשר×\95×\99×\95ת ×©×\9e×\95צ×\92×\95ת ×\91×\93×£ ×\94×\96×\94 ×\90×\9e×\95ר×\95ת ×\9c×¢×\91×\95×\93, ×\90×\91×\9c ×\94Ö¾API ×¢×\93×\99×\99×\9f ×\91פ×\99ת×\95×\97 ×¤×¢×\99×\9c, ×\95×\99×\9b×\95×\9c ×\9c×\94שתנ×\95ת ×\91×\9b×\9c ×\96×\9e×\9f. ×¢×©×\95 ×\9e×\99× ×\95×\99 ×\9c[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ ×¨×©×\99×\9eת ×\94×\93×\99×\95×\95ר mediawiki-api-announce] ×\9c×\94×\95×\93×¢×\95ת ×¢×\9c ×¢×\93×\9b×\95× ×\99×\9d.\n\n<strong>×\91קש×\95ת ×©×\92×\95×\99×\95ת:</strong> ×\9bש×\91קש×\95ת ×©×\92×\95×\99×\95ת × ×©×\9c×\97×\95ת ×\9cÖ¾API, ×ª×\99ש×\9c×\97 ×\9b×\95תרת HTTP ×¢×\9d ×\94×\9eפת×\97 \"MediaWiki-API-Error\" ×\95×\90×\96 ×\92×\9d ×\94ער×\9a ×©×\9c ×\94×\9b×\95תרת ×\95×\92×\9d ×§×\95×\93 ×\94ש×\92×\99×\90×\94 ×\99×\95×\92×\93ר×\95 ×\9c×\90×\95ת×\95 ×¢×¨×\9a. ×\9c×\9e×\99×\93×¢ × ×\95סף ×¨' [[mw:Special:MyLanguage/API:Errors_and_warnings|API: ×©×\92×\99×\90×\95ת ×\95×\90×\96×\94ר×\95ת]].\n\n<strong>×\91×\93×\99ק×\94:</strong> ×\9c×\91×\93×\99ק×\94 ×§×\9c×\94 ×\99×\95תר ×©×\9c ×\91קש×\95ת ×¨' [[Special:ApiSandbox]].",
+       "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|ת×\99×¢×\95×\93]]\n* [[mw:Special:MyLanguage/API:FAQ|ש×\90×\9c×\95ת × ×¤×\95צ×\95ת]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api ×¨×©×\99×\9eת ×\93×\99×\95×\95ר]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce ×\94×\95×\93×¢×\95ת ×¢×\9c API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R ×\91×\90×\92×\99×\9d ×\95×\91קש×\95ת]\n</div>\n<strong>×\9eצ×\91:</strong> ×\94Ö¾API ×©×\9c ×\9e×\93×\99×\94Ö¾×\95×\99ק×\99 ×\94×\95×\90 ×\9e×\9eשק ×\95ת×\99ק ×\95×\99צ×\99×\91 ×©× ×ª×\9e×\9a ×\95×\9eשתפר ×\91×\90×\95פ×\9f ×¡×\93×\99ר. ×\9c×\9eר×\95ת ×©×\90× ×\97× ×\95 ×\9eשת×\93×\9c×\99×\9d ×\9c×\94×\99×\9e× ×¢ ×\9e×\9b×\9a, ×\9cעת×\99×\9d ×¢×\9c×\99× ×\95 ×\9c×\91צע ×©×\99× ×\95×\99×\99×\9d ×©×¢×\9c×\95×\9c×\99×\9d ×\9cש×\91ש ×\93×\91ר×\99×\9d ×\91פ×\95נקצ×\99×\95× ×\9c×\99×\95ת ×\94×\96×\95; ×\91×\90פשר×\95ת×\9a ×\9cעש×\95ת ×\9e×\99× ×\95×\99 ×\9c[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ ×¨×©×\99×\9eת ×\94×\93×\99×\95×\95ר mediawiki-api-announce] ×\9b×\93×\99 ×\9cק×\91×\9c ×\94×\95×\93×¢×\95ת ×¢×\9c ×¢×\93×\9b×\95× ×\99×\9d.\n\n<strong>×\91קש×\95ת ×©×\92×\95×\99×\95ת:</strong> ×\9bש×\91קש×\95ת ×©×\92×\95×\99×\95ת × ×©×\9c×\97×\95ת ×\9cÖ¾API, ×ª×\99ש×\9c×\97 ×\9b×\95תרת HTTP ×¢×\9d ×\94×\9eפת×\97 \"MediaWiki-API-Error\", ×\95×\90×\96 ×\92×\9d ×\94ער×\9a ×©×\9c ×\94×\9b×\95תרת ×\95×\92×\9d ×§×\95×\93 ×\94ש×\92×\99×\90×\94 ×\99×\95×\92×\93ר×\95 ×\9c×\90×\95ת×\95 ×¢×¨×\9a. ×\9c×\9e×\99×\93×¢ × ×\95סף, ×\90פשר ×\9c×¢×\99×\99×\9f ×\91×\93×£ [[mw:Special:MyLanguage/API:Errors_and_warnings|API: ×©×\92×\99×\90×\95ת ×\95×\90×\96×\94ר×\95ת]].\n\n<p class=\"mw-apisandbox-link\"><strong>×\91×\93×\99ק×\94:</strong> ×\9c×\91×\93×\99ק×\94 ×§×\9c×\94 ×\99×\95תר ×©×\9c ×\91קש×\95ת, ×\90פשר ×\9c×\94שת×\9eש ×\91[[Special:ApiSandbox|×\90ר×\92×\96 ×\94×\97×\95×\9c ×©×\9c API]].</p>",
        "apihelp-main-param-action": "איזו פעולה לבצע.",
        "apihelp-main-param-format": "תסדיר הפלט.",
        "apihelp-main-param-maxlag": "שיהוי מרבי יכול לשמש כשמדיה־ויקי מותקנת בצביר עם מסד נתונים משוכפל. כדי לחסוך בפעולות שגורמות יותר שיהוי בשכפול אתר, הפרמטר הזה יכול לגרום ללקוח להמתין עד ששיהוי השכפול יורד מתחת לערך שצוין. במקרה של שיהוי מוגזם, קוד השגיאה <samp>maxlag</samp> מוחזר עם הודעה כמו <samp>Waiting for $host: $lag seconds lagged</samp>.<br />ר' [[mw:Special:MyLanguage/Manual:Maxlag_parameter|מדריך למשתמש: פרמטר maxlag]] למידע נוסף.",
@@ -69,6 +69,7 @@
        "apihelp-compare-param-fromid": "מס׳ זיהוי של הדף הראשון להשוואה.",
        "apihelp-compare-param-fromrev": "גרסה ראשונה להשוואה.",
        "apihelp-compare-param-fromtext": "להשתמש בטקסט הזה במקום תוכן הגרסה שהוגדרה על־ידי <var dir=\"ltr\">fromtitle</var>, <var dir=\"ltr\">fromid</var> או <var dir=\"ltr\">fromrev</var>.",
+       "apihelp-compare-param-fromsection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'from'.",
        "apihelp-compare-param-frompst": "לעשות התמרה לפני שמירה ב־<var>fromtext</var>.",
        "apihelp-compare-param-fromcontentmodel": "מודל התוכן של <var>fromtext</var>. אם זה לא סופק, ייעשה ניחוש על סמך פרמטרים אחרים.",
        "apihelp-compare-param-fromcontentformat": "תסדיר הסדרת תוכן של <var>fromtext</var>.",
@@ -77,6 +78,7 @@
        "apihelp-compare-param-torev": "גרסה שנייה להשוואה.",
        "apihelp-compare-param-torelative": "להשתמש בגרסה יחסית לגרסה שהוסקה מ<var dir=\"ltr\">fromtitle</var>, <var dir=\"ltr\">fromid</var> או <var dir=\"ltr\">fromrev</var>. לכל אפשריות ה־\"to\" האחרות לא תהיה השפעה.",
        "apihelp-compare-param-totext": "להשתמש בטקסט הזה במקום התוכן של הגרסה שהוגדר ב־<var dir=\"ltr\">totitle</var>, <var dir=\"ltr\">toid</var> or <var dir=\"ltr\">torev</var>.",
+       "apihelp-compare-param-tosection": "יש להשתמש רק בפסקה שצוינה בתוכן של הפרמטר 'to'.",
        "apihelp-compare-param-topst": "לעשות התמרה לפני שמירה ב־<var>totext</var>.",
        "apihelp-compare-param-tocontentmodel": "מודל התוכן של <var>totext</var>. אם זה לא סופק, ייעשה ניחוש על סמך פרמטרים אחרים.",
        "apihelp-compare-param-tocontentformat": "תסדיר הסדרת תוכן של <var>fromtext</var>.",
        "apihelp-import-extended-description": "יש לשים לב לכך שפעולת HTTP POST צריכה להיעשות בתור העלאת קובץ (כלומר, עם multipart/form-data) בזמן שליחת קובץ לפרמטר <var>xml</var>.",
        "apihelp-import-param-summary": "תקציר ייבוא עיולי יומן.",
        "apihelp-import-param-xml": "קובץ XML שהועלה.",
+       "apihelp-import-param-interwikiprefix": "לייבוא באמצעות העלאת קבצים: תחילית הבינוויקי שתוצג עבור שמות משתמשים שאינם מוכרים (וגם עבור שמות משתמשים מוכרים אם <var>$1assignknownusers</var> מוגדר).",
+       "apihelp-import-param-assignknownusers": "הקצאת העריכות למשתמשים המקומיים כאשר משתמשים בשמות זהים קיימים באתר המקומי.",
        "apihelp-import-param-interwikisource": "ליבוא בין אתרי ויקי: מאיזה ויקי לייבא.",
        "apihelp-import-param-interwikipage": "ליבוא בין אתרי ויקי: איזה דף לייבא.",
        "apihelp-import-param-fullhistory": "ליבוא בין אתרי ויקי: לייבר את ההיסטוריה המלאה, לא רק את הגרסה הנוכחית.",
        "apihelp-opensearch-summary": "חיפוש בוויקי בפרוטוקול OpenSearch.",
        "apihelp-opensearch-param-search": "מחרוזת לחיפוש.",
        "apihelp-opensearch-param-limit": "המספר המרבי של התוצאות שתוחזרנה.",
-       "apihelp-opensearch-param-namespace": "ש×\9e×\95ת ×\9eת×\97×\9d ×\9c×\97×\99פ×\95ש.",
+       "apihelp-opensearch-param-namespace": "×\9eר×\97×\91×\99 ×\94ש×\9d ×©×\91×\94×\9d ×\99ת×\91צע ×\94×\97×\99פ×\95ש. ×\9cש×\93×\94 ×\96×\94 ×\90×\99×\9f ×\9eש×\9e×¢×\95ת ×\90×\9d <var>$1search</var> ×\9eת×\97×\99×\9c ×¢×\9d ×ª×\97×\99×\9c×\99ת ×ª×§×\99× ×\94 ×©×\9c ×\9eר×\97×\91 ×©×\9d.",
        "apihelp-opensearch-param-suggest": "לא לעשות דבר אם <var>[[mw:Special:MyLanguage/Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> הוא false.",
        "apihelp-opensearch-param-redirects": "איך לטפל בהפניות:\n;return:להחזיר את ההפניה עצמה.\n;resolve:להחזיר את דף היעד. יכול להחזיר פחות מ־$1limit תוצאות.\nמסיבות היסטוריות, בררת המחדל היא \"return\" עבור $1format=json ו־\"resolve\" עבור תסדירים אחרים.",
        "apihelp-opensearch-param-format": "תסדיר הפלט.",
        "apihelp-parse-param-disablepp": "יש להשתמש ב־<var>$1disablelimitreport</var> במקום.",
        "apihelp-parse-param-disableeditsection": "להשמיט את קישורי עריכת הפסקאות מפלט המפענח.",
        "apihelp-parse-param-disabletidy": "לא להריץ ניקוי HTML (למשל tidy) על פלט המפענח.",
+       "apihelp-parse-param-disablestylededuplication": "לא להסיר סגנונות כפולים בפלט של המפענח.",
        "apihelp-parse-param-generatexml": "יצירת עץ פענוח של XML (נדרש מודל תוכן <code>$1</code>; מוחלף ב־<kbd>$2prop=parsetree</kbd>).",
        "apihelp-parse-param-preview": "לפענח במצב תצוגה מקדימה.",
        "apihelp-parse-param-sectionpreview": "לפענח במצב תצוגה מקדימה של פסקה (מדליק גם את מצב תצוגה מקדימה).",
        "apihelp-query+prefixsearch-summary": "ביצוע חיפוש תחילית של כותרות דפים.",
        "apihelp-query+prefixsearch-extended-description": "למרות הדמיון בשם, המודול הזה אינו אמור להיות שווה ל־[[Special:PrefixIndex]] (\"מיוחד:דפים המתחילים ב\"); לדבר כזה, ר' <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd> עם הפרמטר <kbd>apprefix</kbd>. מטרת המודול הזה דומה ל־<kbd>[[Special:ApiHelp/opensearch|action=opensearch]]</kbd>: לקבל קלט ממשתמש ולספק את הכותרות המתאימות ביותר. בהתאם לשרת מנוע החיפוש, זה יכול לכלול תיקון שגיאות כתיב, הימנעות מדפי הפניה והירסטיקות אחרות.",
        "apihelp-query+prefixsearch-param-search": "מחרוזת לחיפוש.",
-       "apihelp-query+prefixsearch-param-namespace": "ש×\9e×\95ת ×\9eת×\97×\9d ×\9c×\97×\99פ×\95ש.",
+       "apihelp-query+prefixsearch-param-namespace": "×\9eר×\97×\91×\99 ×\94ש×\9d ×©×\91×\94×\9d ×\99ת×\91צע ×\94×\97×\99פ×\95ש. ×\9cש×\93×\94 ×\96×\94 ×\90×\99×\9f ×\9eש×\9e×¢×\95ת ×\90×\9d <var>$1search</var> ×\9eת×\97×\99×\9c ×¢×\9d ×ª×\97×\99×\9c×\99ת ×ª×§×\99× ×\94 ×©×\9c ×\9eר×\97×\91 ×©×\9d.",
        "apihelp-query+prefixsearch-param-limit": "מספר התוצאות המרבי להחזרה.",
        "apihelp-query+prefixsearch-param-offset": "מספר תוצאות לדילוג.",
        "apihelp-query+prefixsearch-example-simple": "חיפוש שםות דפים שמתחילים ב־<kbd>meaning</kbd>.",
        "apihelp-query+search-paramvalue-prop-sectiontitle": "הוספת שם הפסקה התואמת.",
        "apihelp-query+search-paramvalue-prop-categorysnippet": "הוספת קטע קצר מפוענח של הקטגוריה התואמת.",
        "apihelp-query+search-paramvalue-prop-isfilematch": "הוספת בוליאני שמציין אם החיפוש תאם לתוכן של קובץ.",
+       "apihelp-query+search-paramvalue-prop-extensiondata": "הוספת נתונים נוספים שנוצרים על־ידי הרחבות.",
        "apihelp-query+search-paramvalue-prop-score": "חסר־השפעה.",
        "apihelp-query+search-paramvalue-prop-hasrelated": "חסר־השפעה.",
        "apihelp-query+search-param-limit": "כמה דפים להחזיר בסך הכול.",
        "apihelp-query+watchlist-paramvalue-prop-parsedcomment": "הוספת ההערכה המפוענחת של העריכה.",
        "apihelp-query+watchlist-paramvalue-prop-timestamp": "הוספת חותם־זמן של העריכה.",
        "apihelp-query+watchlist-paramvalue-prop-patrol": "תיוג עריכות שנבדקו.",
+       "apihelp-query+watchlist-paramvalue-prop-autopatrol": "תיוג עריכות המסומנות כבדוקות באופן אוטומטי.",
        "apihelp-query+watchlist-paramvalue-prop-sizes": "הוספת האורך החדש והישן של הדף.",
        "apihelp-query+watchlist-paramvalue-prop-notificationtimestamp": "הוספת חותם־זמן של ההודעה האחרונה למשתמש על העריכה.",
        "apihelp-query+watchlist-paramvalue-prop-loginfo": "הוספת מידע מהיומן איפה שמתאים.",
+       "apihelp-query+watchlist-paramvalue-prop-tags": "רשימת תגיות עבור הפעולה.",
        "apihelp-query+watchlist-param-show": "הצגה רק של פריטים שמתאימים לאמות המידה האלו. למשל, כדי לראות רק עריכות משניות שעשו משתמשים שנכנסו לחשבון, יש להגדיר $1show=minor|!anon.",
        "apihelp-query+watchlist-param-type": "אולי סוגי שינויים להציג:",
        "apihelp-query+watchlist-paramvalue-type-edit": "עריכות דף רגילות.",
        "apierror-chunk-too-small": "גודל הפלח המזערי הוא {{PLURAL:$1|בית אחד|$1 בתים}} בשביל פלחים לא סופיים.",
        "apierror-cidrtoobroad": "טווחי CIDR של $1 שרחבים יותר מ־/$2 אינם קבילים.",
        "apierror-compare-no-title": "לא ניתן לעשות התמרה לפני שמירה ללא כותרת. נא לנסות לציין <var>fromtitle</var> או <var>totitle</var>.",
+       "apierror-compare-nosuchfromsection": "הפסקה $1 אינה קיימת בתוכן של 'from'.",
+       "apierror-compare-nosuchtosection": "הפסקה $1 אינה קיימת בתוכן של 'to'.",
        "apierror-compare-relative-to-nothing": "אין גרסת \"from\" עבור <var>torelative</var> שתהיה יחסית.",
        "apierror-contentserializationexception": "הסדרת התוכן נכשלה: $1",
        "apierror-contenttoobig": "התוכן שסיפקת חורג מגודל הערך המרבי של {{PLURAL:$1|קילובייט אחד|$1 קילובייטים}}.",
        "apierror-invalidurlparam": "ערך בלתי־תקין עבור <var>$1urlparam</var> (ערך: <kbd>$2=$3</kbd>).",
        "apierror-invaliduser": "שם משתמש בלתי־תקין \"$1\".",
        "apierror-invaliduserid": "מזהה המשתמש <var>$1</var> אינו תקין.",
+       "apierror-maxbytes": "הפרמטר <var>$1</var> לא יכול להיות ארוך יותר {{PLURAL:$2|מבייט אחד|מ־$2 בייטים}}",
+       "apierror-maxchars": "הפרמטר <var>$1</var> לא יכול להיות ארוך יותר {{PLURAL:$2|מתו אחד|מ־$2 תווים}}",
        "apierror-maxlag-generic": "ממתין לשרת מסד נתונים: עיכוב של {{PLURAL:$1|שנייה אחת|$1 שניות}}.",
        "apierror-maxlag": "ממתין ל־$2: שיהוי של {{PLURAL:$1|שנייה אחת|$1 שניות}}.",
        "apierror-mimesearchdisabled": "חיפוש MIME כבוי במצב קמצן.",
index f33e2d5..84e1046 100644 (file)
@@ -45,6 +45,7 @@
        "apihelp-block-param-tags": "ブロック記録の項目に適用する変更タグ。",
        "apihelp-block-example-ip-simple": "IPアドレス <kbd>192.0.2.5</kbd> を <kbd>First strike<kbd> という理由で3日ブロックする",
        "apihelp-block-example-user-complex": "利用者 <kbd>Vandal</kbd> を <kbd>Vandalism</kbd> という理由で無期限ブロックし、新たなアカウント作成とメールの送信を禁止する。",
+       "apihelp-changeauthenticationdata-summary": "現在の利用者の認証データを変更します。",
        "apihelp-changeauthenticationdata-example-password": "現在の利用者のパスワードを <kbd>ExamplePassword</kbd> に変更する。",
        "apihelp-checktoken-summary": "<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> のトークンの妥当性を確認します。",
        "apihelp-checktoken-param-type": "調べるトークンの種類。",
        "apihelp-checktoken-example-simple": "<kbd>csrf</kbd> トークンの妥当性を調べる。",
        "apihelp-clearhasmsg-summary": "現在の利用者の <code>hasmsg</code> フラグを消去します。",
        "apihelp-clearhasmsg-example-1": "現在の利用者の <code>hasmsg</code> フラグを消去する。",
+       "apihelp-clientlogin-summary": "インタラクティブフローを使用してウィキにログインします。",
        "apihelp-clientlogin-example-login": "利用者 <kbd>Example</kbd> としてのログイン処理をパスワード <kbd>ExamplePassword</kbd> で開始する",
+       "apihelp-clientlogin-example-login2": "<kbd>987654</kbd>の<var>OATHToken</var>を提供する2段階認証の<samp>UI</samp>レスポンスの後にログインを続けます。",
        "apihelp-compare-summary": "2つの版間の差分を取得します。",
        "apihelp-compare-extended-description": "\"from\" と \"to\" の両方の版番号、ページ名、もしくはページIDを渡す必要があります。",
        "apihelp-compare-param-fromtitle": "比較する1つ目のページ名。",
        "apihelp-compare-param-fromid": "比較する1つ目のページID。",
        "apihelp-compare-param-fromrev": "比較する1つ目の版。",
+       "apihelp-compare-param-frompst": "<var>fromtext</var>に保存前変換を行います。",
+       "apihelp-compare-param-fromcontentmodel": "<var>fromtext</var>のコンテンツモデル。指定されていない場合は、他のパラメータに基づいて推測されます。",
        "apihelp-compare-param-totitle": "比較する2つ目のページ名。",
        "apihelp-compare-param-toid": "比較する2つ目のページID。",
        "apihelp-compare-param-torev": "比較する2つ目の版。",
+       "apihelp-compare-param-topst": "<var>totext</var>に保存前変換を行います。",
        "apihelp-compare-param-prop": "どの情報を取得するか:",
        "apihelp-compare-paramvalue-prop-diff": "差分HTML。",
        "apihelp-compare-paramvalue-prop-diffsize": "差分HTMLのサイズ (バイト数)。",
@@ -74,6 +80,7 @@
        "apihelp-compare-paramvalue-prop-size": "'from' および 'to' の版のサイズ。",
        "apihelp-compare-example-1": "版1と2の差分を生成する。",
        "apihelp-createaccount-summary": "新しい利用者アカウントを作成します。",
+       "apihelp-createaccount-example-create": "利用者 <kbd>Example</kbd> を作成する処理をパスワード <kbd>ExamplePassword</kbd> で開始する",
        "apihelp-createaccount-param-name": "利用者名。",
        "apihelp-createaccount-param-password": "パスワード (<var>$1mailpassword</var> が設定されると無視されます)。",
        "apihelp-createaccount-param-domain": "外部認証のドメイン (省略可能)。",
@@ -85,6 +92,7 @@
        "apihelp-createaccount-param-language": "利用者の言語コードの既定値 (省略可能, 既定ではコンテンツ言語)。",
        "apihelp-createaccount-example-pass": "利用者 <kbd>testuser</kbd> をパスワード <kbd>test123</kbd> として作成する。",
        "apihelp-createaccount-example-mail": "利用者 <kbd>testmailuser</kbd>を作成し、無作為に生成されたパスワードをメールで送る。",
+       "apihelp-cspreport-param-source": "このレポートをトリガしたCSPヘッダを生成した内容",
        "apihelp-delete-summary": "ページを削除します。",
        "apihelp-delete-param-title": "削除するページ名です。<var>$1pageid</var> とは同時に使用できません。",
        "apihelp-delete-param-pageid": "削除するページIDです。<var>$1title</var> とは同時に使用できません。",
        "apihelp-edit-param-bot": "この編集をボットの編集としてマークする。",
        "apihelp-edit-param-basetimestamp": "編集前の版のタイムスタンプ。編集競合を検出するために使用されます。\n[[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]] で取得できます。",
        "apihelp-edit-param-starttimestamp": "編集作業を開始したときのタイムスタンプ。編集競合を検出するために使用されます。適切な値は <var>[[Special:ApiHelp/main|curtimestamp]]</var> を使用して編集作業を開始するとき (たとえば、編集するページの本文を読み込んだとき) に取得できます。",
+       "apihelp-edit-param-recreate": "その間に削除されたページに関するエラーを上書きします。",
        "apihelp-edit-param-createonly": "すでにそのページが存在する場合は編集を行いません。",
        "apihelp-edit-param-nocreate": "そのページが存在しない場合にエラーを返します。",
        "apihelp-edit-param-watch": "そのページを現在の利用者のウォッチリストに追加します。",
        "apihelp-edit-param-undo": "この版を取り消します。$1text, $1prependtext および $1appendtext をオーバーライドします。",
        "apihelp-edit-param-undoafter": "$1undo からこの版までのすべての版を取り消します。設定しない場合、ひとつの版のみ取り消されます。",
        "apihelp-edit-param-redirect": "自動的に転送を解決します。",
+       "apihelp-edit-param-contentmodel": "新しいコンテンツのコンテンツ・モデル。",
        "apihelp-edit-param-token": "このトークンは常に最後のパラメーターとして、または少なくとも $1text パラメーターより後に送信されるべきです。",
        "apihelp-edit-example-edit": "ページを編集",
        "apihelp-edit-example-prepend": "<kbd>_&#95;NOTOC_&#95;</kbd> をページの先頭に挿入する。",
        "apihelp-expandtemplates-param-title": "ページの名前です。",
        "apihelp-expandtemplates-param-text": "変換するウィキテキストです。",
        "apihelp-expandtemplates-paramvalue-prop-wikitext": "展開されたウィキテキスト。",
+       "apihelp-expandtemplates-paramvalue-prop-jsconfigvars": "ページに固有のJavaScriptの設定変数を提供します。",
+       "apihelp-expandtemplates-paramvalue-prop-encodedjsconfigvars": "JSON文字列としてページに固有のJavaScriptの設定変数を提供します。",
        "apihelp-expandtemplates-paramvalue-prop-parsetree": "入力のXML構文解析ツリー。",
        "apihelp-expandtemplates-param-includecomments": "HTMLコメントを出力に含めるかどうか。",
        "apihelp-expandtemplates-param-generatexml": "XMLの構文解析ツリーを生成します (replaced by $1prop=parsetree)",
        "apihelp-feedrecentchanges-param-namespace": "この名前空間の結果のみに絞り込む。",
        "apihelp-feedrecentchanges-param-invert": "選択されたものを除く、すべての名前空間。",
        "apihelp-feedrecentchanges-param-associated": "関連する(トークまたはメイン)名前空間を含めます。",
+       "apihelp-feedrecentchanges-param-days": "結果を絞り込む日数。",
        "apihelp-feedrecentchanges-param-limit": "返す結果の最大数。",
        "apihelp-feedrecentchanges-param-from": "これ以降の編集を表示する。",
        "apihelp-feedrecentchanges-param-hideminor": "細部の変更を隠す。",
        "apihelp-feedrecentchanges-param-hideliu": "登録利用者による変更を隠す。",
        "apihelp-feedrecentchanges-param-hidepatrolled": "巡回済みの変更を隠す。",
        "apihelp-feedrecentchanges-param-hidemyself": "現在の利用者による編集を非表示にする。",
+       "apihelp-feedrecentchanges-param-hidecategorization": "カテゴリのメンバーの変更を非表示にする。",
        "apihelp-feedrecentchanges-param-tagfilter": "タグにより絞り込む。",
        "apihelp-feedrecentchanges-param-target": "このページからリンクされているページの変更のみを表示する。",
+       "apihelp-feedrecentchanges-param-showlinkedto": "選択したページへのリンク元での変更の表示に切り替え",
        "apihelp-feedrecentchanges-example-simple": "最近の更新を表示する。",
        "apihelp-feedrecentchanges-example-30days": "最近30日間の変更を表示する。",
        "apihelp-feedwatchlist-summary": "ウォッチリストのフィードを返します。",
        "apihelp-help-example-query": "2つの下位モジュールのヘルプ",
        "apihelp-imagerotate-summary": "1つ以上の画像を回転させます。",
        "apihelp-imagerotate-param-rotation": "画像を回転させる時計回りの角度。",
+       "apihelp-imagerotate-param-tags": "アップロード記録の項目に適用するタグ。",
        "apihelp-imagerotate-example-simple": "<kbd>File:Example.png</kbd> を <kbd>90</kbd> 度回転させる。",
        "apihelp-imagerotate-example-generator": "<kbd>Category:Flip</kbd> 内のすべての画像を <kbd>180</kbd> 度回転させる。",
        "apihelp-import-summary": "他のWikiまたはXMLファイルからページを取り込む。",
        "apihelp-import-extended-description": "<var>xml</var> パラメーターでファイルを送信する場合、ファイルのアップロードとしてHTTP POSTされなければならない (例えば、multipart/form-dataを使用する) 点に注意してください。",
        "apihelp-import-param-summary": "記録されるページ取り込みの要約。",
        "apihelp-import-param-xml": "XMLファイルをアップロード",
+       "apihelp-import-param-assignknownusers": "指定されたユーザーがこのウィキに存在する場合そのユーザーに編集を割り当てる",
        "apihelp-import-param-interwikisource": "ウィキ間の取り込みの場合: 取り込み元のウィキ。",
        "apihelp-import-param-interwikipage": "ウィキ間の取り込みの場合: 取り込むページ。",
        "apihelp-import-param-fullhistory": "ウィキ間の取り込みの場合: 現在の版のみではなく完全な履歴を取り込む。",
        "apihelp-import-param-rootpage": "このページの下位ページとして取り込む。<var>$1namespace</var> パラメータとは同時に使用できません。",
        "apihelp-import-example-import": "[[meta:Help:ParserFunctions]] をすべての履歴とともに名前空間100に取り込む。",
        "apihelp-login-summary": "ログインして認証クッキーを取得します。",
-       "apihelp-login-extended-description": "ã\83­ã\82°ã\82¤ã\83³ã\81\8cæ\88\90å\8a\9fã\81\97ã\81\9få ´å\90\88ã\80\81å¿\85è¦\81ã\81ªã\82¯ã\83\83ã\82­ã\83¼ã\81¯ HTTP å¿\9cç­\94ã\83\98ã\83\83ã\83\80ã\81«å\90«ã\81¾ã\82\8cã\81¾ã\81\99ã\80\82ã\83­ã\82°ã\82¤ã\83³ã\81«å¤±æ\95\97ã\81\97ã\81\9få ´å\90\88ã\80\81è\87ªå\8b\95å\8c\96ã\81®ã\83\91ã\82¹ã\83¯ã\83¼ã\83\89æ\8e¨å®\9aæ\94»æ\92\83ã\82\92å\88¶é\99\90ã\81\99ã\82\8bã\81\9fã\82\81ã\81«ã\80\81追å\8a ã\81®è©¦è¡\8cã\81¯é\80\9f度å\88¶é\99\90ã\81\95ã\82\8cã\82\8bã\81\93ã\81¨ã\81\8cã\81\82ã\82\8aます。",
+       "apihelp-login-extended-description": "ã\81\93ã\81®ã\82¢ã\82¯ã\82·ã\83§ã\83³ã\81¯ã\80\81[[Special:BotPasswords]]ã\81¨çµ\84ã\81¿å\90\88ã\82\8fã\81\9bã\81¦ä½¿ç\94¨ã\81\99ã\82\8bå¿\85è¦\81ã\81\8cã\81\82ã\82\8aã\81¾ã\81\99ã\80\82ã\83¡ã\82¤ã\83³ã\82¢ã\82«ã\82¦ã\83³ã\83\88ã\81®ã\83­ã\82°ã\82¤ã\83³ã\81«ä½¿ç\94¨ã\81\99ã\82\8bã\81\93ã\81¨ã\81¯æ\8e¨å¥¨ã\81\95ã\82\8cã\81ªã\81\8fã\81ªã\82\8aã\80\81è­¦å\91\8aã\81ªã\81\8f失æ\95\97ã\81\99ã\82\8bå\8f¯è\83½æ\80§ã\81\8cã\81\82ã\82\8aã\81¾ã\81\99ã\80\82ã\83¡ã\82¤ã\83³ã\82¢ã\82«ã\82¦ã\83³ã\83\88ã\81«å®\89å\85¨ã\81«ã\83­ã\82°ã\82¤ã\83³ã\81\99ã\82\8bã\81«ã\81¯ã\80\81<kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>ã\82\92使ç\94¨ã\81\97ます。",
        "apihelp-login-param-name": "利用者名。",
        "apihelp-login-param-password": "パスワード。",
        "apihelp-login-param-domain": "ドメイン (省略可能)",
        "apihelp-managetags-param-tag": "作成、削除、有効化、または無効化するタグ。タグの作成の場合、そのタグは存在しないものでなければなりません。タグの削除の場合、そのタグが存在しなければなりません。タグの有効化の場合、そのタグが存在し、かつ拡張機能によって使用されていないものでなければなりません。タグの無効化の場合、そのタグが現在有効であって手動で定義されたものでなければなりません。",
        "apihelp-managetags-param-reason": "タグを作成、削除、有効化、または無効化する追加の理由。",
        "apihelp-managetags-param-ignorewarnings": "操作中に発生したすべての警告を無視するかどうか。",
+       "apihelp-managetags-param-tags": "タグを変更し、タグ管理記録の項目に適用します。",
        "apihelp-managetags-example-create": "<kbd>spam</kbd> という名前のタグを <kbd>For use in edit patrolling</kbd> という理由で作成する",
        "apihelp-managetags-example-delete": "<kbd>vandlaism</kbd> タグを <kbd>Misspelt</kbd> という理由で削除する",
        "apihelp-managetags-example-activate": "<kbd>spam</kbd> という名前のタグを <kbd>For use in edit patrolling</kbd> という理由で有効化する",
        "apihelp-purge-param-forcelinkupdate": "リンクテーブルを更新します。",
        "apihelp-purge-example-simple": "ページ <kbd>Main Page</kbd> および <kbd>API</kbd> をパージする。",
        "apihelp-purge-example-generator": "標準名前空間にある最初の10ページをパージする。",
+       "apihelp-query-summary": "MediaWikiからデータを取得します。",
        "apihelp-query-param-prop": "照会ページ用に、どのプロパティを取得するか。",
        "apihelp-query-param-list": "どの一覧を取得するか。",
        "apihelp-query-param-meta": "どのメタデータを取得するか。",
        "apihelp-query+allfileusages-param-from": "列挙を開始するファイルのページ名。",
        "apihelp-query+allfileusages-param-to": "列挙を終了するファイルのページ名。",
        "apihelp-query+allfileusages-param-prefix": "この値で始まるページ名のすべてのファイルを検索する。",
+       "apihelp-query+allfileusages-param-unique": "ファイル名を一度だけ表示します。<kbd>$1prop=ids</kbd> とは同時に使用できません。ジェネレーターとして使用される場合、リンク元ではなくリンク先のページを生成します。",
        "apihelp-query+allfileusages-param-prop": "どの情報を結果に含めるか:",
        "apihelp-query+allfileusages-paramvalue-prop-ids": "使用しているページのページIDを追加します ($1unique とは同時に使用できません)。",
        "apihelp-query+allfileusages-paramvalue-prop-title": "ファイルのページ名を追加します。",
        "apihelp-query+allpages-param-to": "列挙を終了するページ名。",
        "apihelp-query+allpages-param-prefix": "この値で始まるすべてのページ名を検索します。",
        "apihelp-query+allpages-param-namespace": "列挙する名前空間。",
+       "apihelp-query+allpages-param-filterredir": "リストするページ",
        "apihelp-query+allpages-param-minsize": "ページの最低バイト数を制限する。",
        "apihelp-query+allpages-param-maxsize": "ページの最大バイト数を制限する。",
        "apihelp-query+allpages-param-prtype": "保護されているページに絞り込む。",
        "apihelp-query+allredirects-param-prefix": "この値で始まるすべてのページを検索する。",
        "apihelp-query+allredirects-param-unique": "転送先ページ名を一度だけ表示します。<kbd>$1prop=ids|fragment|interwiki</kbd> とは同時に使用できません。ジェネレーターとして使用される場合、転送元ではなく転送先のページを生成します。",
        "apihelp-query+allredirects-param-prop": "どの情報を結果に含めるか:",
+       "apihelp-query+allredirects-paramvalue-prop-ids": "転送ページのページIDを追加します ($1unique とは同時に使用できません)。",
        "apihelp-query+allredirects-paramvalue-prop-title": "転送ページのページ名を追加します。",
        "apihelp-query+allredirects-param-namespace": "列挙する名前空間。",
        "apihelp-query+allredirects-param-limit": "返す項目の総数。",
        "apihelp-query+allredirects-param-dir": "一覧表示する方向。",
        "apihelp-query+allredirects-example-B": "<kbd>B</kbd> で始まる転送先ページ (存在しないページも含む)を、転送元のページIDとともに表示する。",
+       "apihelp-query+allredirects-example-unique": "一意のターゲットページを一覧表示します。",
+       "apihelp-query+allredirects-example-unique-generator": "存在しないものに印をつけて、すべて取得する。",
+       "apihelp-query+allredirects-example-generator": "リダイレクトを含むページを取得します。",
        "apihelp-query+allrevisions-summary": "すべての版を一覧表示する。",
        "apihelp-query+allrevisions-param-start": "列挙の始点となるタイムスタンプ。",
        "apihelp-query+allrevisions-param-end": "列挙の終点となるタイムスタンプ。",
        "apihelp-query+prefixsearch-param-namespace": "検索する名前空間。<var>$1search</var>が有効な名前空間接頭辞で始まる場合は無視されます。",
        "apihelp-query+prefixsearch-param-limit": "返す結果の最大数。",
        "apihelp-query+prefixsearch-example-simple": "<kbd>meaning</kbd> で始まるページ名を検索する。",
+       "apihelp-query+prefixsearch-param-profile": "使用するプロファイルを検索します。",
        "apihelp-query+protectedtitles-summary": "作成保護が掛けられているページを一覧表示します。",
        "apihelp-query+protectedtitles-param-namespace": "この名前空間に含まれるページのみを一覧表示します。",
        "apihelp-query+protectedtitles-param-level": "この保護レベルのページのみを一覧表示します。",
        "apihelp-query+querypage-param-page": "特別ページの名前です。これは大文字小文字を区別することに注意。",
        "apihelp-query+querypage-param-limit": "返す結果の数。",
        "apihelp-query+querypage-example-ancientpages": "[[Special:Ancientpages]] の結果を返す。",
+       "apihelp-query+random-summary": "ランダムなページのセットを取得します。",
        "apihelp-query+random-param-namespace": "この名前空間にあるページのみを返します。",
        "apihelp-query+random-param-limit": "返す無作為なページの数を制限する。",
        "apihelp-query+random-param-redirect": "代わりに <kbd>$1filterredir=redirects</kbd> を使用してください。",
        "apihelp-query+recentchanges-paramvalue-prop-redirect": "編集されたページが転送ページである場合、印を付けます。",
        "apihelp-query+recentchanges-paramvalue-prop-patrolled": "巡回可能な編集について、巡回済みかどうか印を付けます。",
        "apihelp-query+recentchanges-paramvalue-prop-loginfo": "記録項目に記録の情報 (記録ID,  記録タイプなど) を追加します。",
+       "apihelp-query+recentchanges-paramvalue-prop-tags": "エントリのタグを一覧表示します。",
        "apihelp-query+recentchanges-param-token": "代わりに <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> を使用してください。",
        "apihelp-query+recentchanges-param-limit": "返す変更の総数。",
        "apihelp-query+recentchanges-param-toponly": "最新の版である変更のみを一覧表示する。",
        "apihelp-query+search-param-search": "この値を含むページ名または本文を検索します。Wikiの検索バックエンド実装に応じて、あなたは特別な検索機能を呼び出すための文字列を検索することができます。",
        "apihelp-query+search-param-namespace": "この名前空間内のみを検索します。",
        "apihelp-query+search-param-what": "実行する検索の種類です。",
+       "apihelp-query+search-param-info": "どのメタデータを返すか。",
        "apihelp-query+search-param-prop": "返すプロパティ:",
        "apihelp-query+search-paramvalue-prop-size": "バイト単位のページのサイズを追加します。",
        "apihelp-query+search-paramvalue-prop-wordcount": "ページのワード数を追加します。",
        "apihelp-query+templates-summary": "与えられたページでトランスクルードされているすべてのページを返します。",
        "apihelp-query+templates-param-namespace": "この名前空間のテンプレートのみ表示する。",
        "apihelp-query+templates-param-limit": "返すテンプレートの数。",
+       "apihelp-query+templates-param-dir": "一覧表示する方向。",
        "apihelp-query+templates-example-simple": "<kbd>Main Page</kbd> で使用されているテンプレートを取得する。",
        "apihelp-query+templates-example-generator": "<kbd>Main Page</kbd> で使用されているテンプレートに関する情報を取得する。",
        "apihelp-query+templates-example-namespaces": "<kbd>Main Page</kbd> でトランスクルードされている {{ns:user}} および {{ns:template}} 名前空間のページを取得する。",
        "apihelp-query+transcludedin-param-prop": "取得するプロパティ:",
        "apihelp-query+transcludedin-paramvalue-prop-pageid": "各ページのページID。",
        "apihelp-query+transcludedin-paramvalue-prop-title": "各ページのページ名。",
+       "apihelp-query+transcludedin-paramvalue-prop-redirect": "ページがリダイレクトである場合マークします。",
+       "apihelp-query+transcludedin-param-namespace": "この名前空間に含まれるページのみを一覧表示します。",
+       "apihelp-query+transcludedin-param-limit": "返す数。",
        "apihelp-query+transcludedin-example-simple": "<kbd>Main Page</kbd> をトランスクルードしているページの一覧を取得する。",
        "apihelp-query+transcludedin-example-generator": "<kbd>Main Page</kbd> をトランスクルードしているページに関する情報を取得する。",
        "apihelp-query+usercontribs-summary": "利用者によるすべての編集を取得します。",
        "apihelp-revisiondelete-summary": "版の削除および復元を行います。",
        "apihelp-revisiondelete-param-reason": "削除または復元の理由。",
        "apihelp-revisiondelete-example-revision": "<kbd>Main Page</kbd> の版 <kbd>12345</kbd> の本文を隠す。",
+       "apihelp-rollback-summary": "ページの最後の編集を取り消す。",
        "apihelp-rollback-param-title": "巻き戻すページ名です。<var>$1pageid</var> とは同時に使用できません。",
        "apihelp-rollback-param-pageid": "巻き戻すページのページIDです。<var>$1title</var> とは同時に使用できません。",
        "apihelp-rollback-param-tags": "巻き戻しに適用するタグ。",
        "apihelp-tokens-example-edit": "編集トークンを取得する (既定)。",
        "apihelp-unblock-summary": "利用者のブロックを解除します。",
        "apihelp-unblock-param-id": "解除するブロックのID (<kbd>list=blocks</kbd>で取得できます)。<var>$1user</var> または <var>$1userid</var> とは同時に使用できません。",
-       "apihelp-unblock-param-user": "ã\83\96ã\83­ã\83\83ã\82¯ã\82\92解é\99¤ã\81\99ã\82\8bå\88©ç\94¨è\80\85å\90\8dã\80\81IPã\82¢ã\83\89ã\83¬ã\82¹ã\81¾ã\81\9fã\81¯IPã\83¬ã\83³ã\82¸ã\80\82<var>$1id</var>とは同時に使用できません。",
+       "apihelp-unblock-param-user": "ã\83\96ã\83­ã\83\83ã\82¯ã\82\92解é\99¤ã\81\99ã\82\8bå\88©ç\94¨è\80\85å\90\8dã\80\81IPã\82¢ã\83\89ã\83¬ã\82¹ã\81¾ã\81\9fã\81¯IPã\82¢ã\83\89ã\83¬ã\82¹ã\83¬ã\83³ã\82¸ã\80\82<var>$1id</var>ã\81¾ã\81\9fã\81¯<var>$1userid</var>とは同時に使用できません。",
        "apihelp-unblock-param-reason": "ブロック解除の理由。",
        "apihelp-unblock-param-tags": "ブロック記録の項目に適用する変更タグ。",
        "apihelp-unblock-example-id": "ブロックID #<kbd>105</kbd> を解除する。",
        "api-help-flag-writerights": "このモジュールは書き込みの権限を必要とします。",
        "api-help-flag-mustbeposted": "このモジュールは POST リクエストのみを受け付けます。",
        "api-help-flag-generator": "このモジュールはジェネレーターとして使用できます。",
+       "api-help-source": "ソース: $1",
        "api-help-parameters": "{{PLURAL:$1|パラメーター}}:",
        "api-help-param-deprecated": "廃止予定です。",
        "api-help-param-required": "このパラメーターは必須です。",
        "api-help-permissions": "{{PLURAL:$1|権限}}:",
        "api-help-permissions-granted-to": "{{PLURAL:$1|権限を持つグループ}}: $2",
        "api-help-open-in-apisandbox": "<small>[サンドボックスで開く]</small>",
+       "apierror-filedoesnotexist": "ファイルが存在しません。",
+       "apierror-invaliduser": "無効なユーザー名「$1」。",
        "apierror-missingparam": "パラメーター <var>$1</var> を設定してください。",
+       "apierror-mustbeloggedin": "$1にログインしている必要があります。",
+       "apierror-noimageredirect": "画像のリダイレクトを作成する権限がありません。",
+       "apierror-nosuchpageid": "ID $1のページはありません。",
+       "apierror-permissiondenied": "$1に必要な権限がありません。",
+       "apierror-permissiondenied-generic": "アクセスが拒否されました。",
+       "apierror-readonly": "ウィキは現在読み取り専用モードです。",
        "apierror-timeout": "サーバーが決められた時間内に応答しませんでした。",
+       "apierror-unknownerror-editpage": "不明な編集ページのエラー:$1",
+       "apierror-unknownerror-nocode": "不明なエラーです。",
+       "apierror-unknownerror": "不明なエラー:「$1」",
        "apiwarn-invalidcategory": "「$1」はカテゴリではありません。",
        "apiwarn-notfile": "「$1」はファイルではありません。",
+       "apiwarn-validationfailed-cannotset": "このモジュールでは設定できません。",
+       "apiwarn-validationfailed-keytoolong": "キーが長すぎます($1バイト以上は許可されません)。",
+       "apiwarn-wgDebugAPI": "<strong>セキュリティ警告</strong>:<var>$wgDebugAPI</var>が有効です。",
+       "api-feed-error-title": "エラー ($1)",
+       "api-usage-docref": "APIの使用については$1を参照してください。",
+       "api-exception-trace": "$2の$1($3)\n$4",
        "api-credits-header": "クレジット",
        "api-credits": "API の開発者:\n* Roan Kattouw (2007年9月-2009年の主任開発者)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (作成者、2006年9月-2007年9月の主任開発者)\n* Brad Jorsch (2013年-現在の主任開発者)\n\nコメント、提案、質問は mediawiki-api@lists.wikimedia.org にお送りください。\nバグはこちらへご報告ください: https://phabricator.wikimedia.org/"
 }
index b61d201..ebf998b 100644 (file)
        "api-help-permissions": "{{PLURAL:$1|權限}}:",
        "api-help-permissions-granted-to": "{{PLURAL:$1|已授權給}}: $2",
        "api-help-authmanager-general-usage": "使用此模組的一般程式是:\n# 通過<kbd>amirequestsfor=$4</kbd>取得來自<kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd>的可用欄位,和來自<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>的<kbd>$5</kbd>令牌。\n# 向用戶顯示欄位,並獲得其提交的內容。\n# 提交(POST)至此模組,提供<var>$1returnurl</var>及任何相關欄位。\n# 在回应中檢查<samp>status</samp>。\n#* 如果您收到了<samp>PASS</samp>(成功)或<samp>FAIL</samp>(失敗),則認為操作結束。成功與否如上句所示。\n#* 如果您收到了<samp>UI</samp>,向用戶顯示新欄位,並再次獲取其提交的內容。然後再次使用<var>$1continue</var>,向本模組提交相關欄位,並重復第四步。\n#* 如果您收到了<samp>REDIRECT</samp>,將使用者指向<samp>redirecttarget</samp>中的目標,等待其返回<var>$1returnurl</var>。然後再次使用<var>$1continue</var>,向本模組提交返回URL中提供的一切欄位,並重復第四步。\n#* 如果您收到了<samp>RESTART</samp>,這意味著身份驗證正常運作,但我們沒有連結的使用者賬戶。您可以將此看做<samp>UI</samp>或<samp>FAIL</samp>。",
+       "apierror-missingparam": "<var>$1</var>參數必須被設定。",
        "apierror-mustbeloggedin-changeauth": "必須登入,才能變更身分核對資取。",
        "apierror-mustbeloggedin-removeauth": "必須登入,才能移除身分核對資取。",
        "apierror-permissiondenied": "您沒有權限$1。",
diff --git a/includes/api/i18n/zh-hk.json b/includes/api/i18n/zh-hk.json
new file mode 100644 (file)
index 0000000..5ea1800
--- /dev/null
@@ -0,0 +1,15 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Liuxinyu970226"
+               ]
+       },
+       "apihelp-block-param-hidename": "隱藏封鎖日誌的用戶名稱。 (需要 \"hideuser\" 權限)。",
+       "apihelp-block-param-allowusertalk": "允許用戶編輯自己的對話頁面 (依據 <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var> 的設定)。",
+       "apihelp-block-param-reblock": "若用戶已被封鎖,覆寫既有的封鎖設定值。",
+       "apihelp-block-param-watchuser": "監視用戶或 IP 的用戶頁面與對話頁面。",
+       "apihelp-createaccount-summary": "建立一個新用戶戶口。",
+       "apihelp-login-param-name": "用戶名稱。",
+       "apihelp-userrights-param-user": "用戶名稱。",
+       "apihelp-userrights-param-userid": "用戶 ID。"
+}
index 8e8b93f..eea8af3 100644 (file)
@@ -78,6 +78,16 @@ class RecentChange {
        const PRC_PATROLLED = 1;
        const PRC_AUTOPATROLLED = 2;
 
+       /**
+        * @var bool For save() - save to the database only, without any events.
+        */
+       const SEND_NONE = true;
+
+       /**
+        * @var bool For save() - do emit the change to RCFeeds (usually public).
+        */
+       const SEND_FEED = false;
+
        public $mAttribs = [];
        public $mExtra = [];
 
@@ -347,11 +357,23 @@ class RecentChange {
 
        /**
         * Writes the data in this object to the database
-        * @param bool $noudp
+        *
+        * For compatibility reasons, the SEND_ constants internally reference a value
+        * that may seem negated from their purpose (none=true, feed=false). This is
+        * because the parameter used to be called "$noudp", defaulting to false.
+        *
+        * @param bool $send self::SEND_FEED or self::SEND_NONE
         */
-       public function save( $noudp = false ) {
+       public function save( $send = self::SEND_FEED ) {
                global $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker;
 
+               if ( is_string( $send ) ) {
+                       // Callers used to pass undocumented strings like 'noudp'
+                       // or 'pleasedontudp' instead of self::SEND_NONE (true).
+                       // @deprecated since 1.31 Use SEND_NONE instead.
+                       $send = self::SEND_NONE;
+               }
+
                $dbw = wfGetDB( DB_MASTER );
                if ( !is_array( $this->mExtra ) ) {
                        $this->mExtra = [];
@@ -425,8 +447,8 @@ class RecentChange {
                                $this->mAttribs['rc_this_oldid'], $this->mAttribs['rc_logid'], null, $this );
                }
 
-               # Notify external application via UDP
-               if ( !$noudp ) {
+               if ( $send === self::SEND_FEED ) {
+                       // Emit the change to external applications via RCFeeds.
                        $this->notifyRCFeeds();
                }
 
@@ -884,7 +906,7 @@ class RecentChange {
                        'rc_last_oldid' => 0,
                        'rc_bot' => $user->isAllowed( 'bot' ) ? (int)$wgRequest->getBool( 'bot', true ) : 0,
                        'rc_ip' => self::checkIPAddress( $ip ),
-                       'rc_patrolled' => $markPatrolled ? self::PRC_PATROLLED : self::PRC_UNPATROLLED,
+                       'rc_patrolled' => $markPatrolled ? self::PRC_AUTOPATROLLED : self::PRC_UNPATROLLED,
                        'rc_new' => 0, # obsolete
                        'rc_old_len' => null,
                        'rc_new_len' => null,
@@ -970,7 +992,7 @@ class RecentChange {
                        'rc_last_oldid' => $oldRevId,
                        'rc_bot' => $bot ? 1 : 0,
                        'rc_ip' => self::checkIPAddress( $ip ),
-                       'rc_patrolled' => self::PRC_PATROLLED, // Always patrolled, just like log entries
+                       'rc_patrolled' => self::PRC_AUTOPATROLLED, // Always patrolled, just like log entries
                        'rc_new' => 0, # obsolete
                        'rc_old_len' => null,
                        'rc_new_len' => null,
index 3947f4b..beac91e 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 
+use MediaWiki\MediaWikiServices;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\LoadBalancer;
 
@@ -59,7 +60,7 @@ abstract class DBAccessBase implements IDBAccessObject {
         * @return IDatabase
         */
        protected function getConnection( $id, $groups = [] ) {
-               $loadBalancer = wfGetLB( $this->wiki );
+               $loadBalancer = $this->getLoadBalancer();
 
                return $loadBalancer->getConnection( $id, $groups, $this->wiki );
        }
@@ -83,13 +84,14 @@ abstract class DBAccessBase implements IDBAccessObject {
        /**
         * Get the database type used for read operations.
         *
-        * @see wfGetLB
+        * @see MediaWikiServices::getDBLoadBalancer
         *
         * @since 1.21
         *
         * @return LoadBalancer The database load balancer object
         */
        public function getLoadBalancer() {
-               return wfGetLB( $this->wiki );
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               return $lbFactory->getMainLB( $this->wiki );
        }
 }
index 9b25d53..8543c4b 100644 (file)
@@ -36,11 +36,14 @@ use Wikimedia\Rdbms\LoadBalancer;
  * Updates that work through this system will be more likely to complete by the time the client
  * makes their next request after this one than with the JobQueue system.
  *
- * In CLI mode, updates run immediately if no DB writes are pending. Otherwise, they run when:
- *   - a) Any waitForReplication() call if no writes are pending on any DB
- *   - b) A commit happens on Maintenance::getDB( DB_MASTER ) if no writes are pending on any DB
- *   - c) EnqueueableDataUpdate tasks may enqueue on commit of Maintenance::getDB( DB_MASTER )
- *   - d) At the completion of Maintenance::execute()
+ * In CLI mode, deferred updates will run:
+ *   - a) During DeferredUpdates::addUpdate if no LBFactory DB handles have writes pending
+ *   - b) On commit of an LBFactory DB handle if no other such handles have writes pending
+ *   - c) During an LBFactory::waitForReplication call if no LBFactory DBs have writes pending
+ *   - d) When the queue is large and an LBFactory DB handle commits (EnqueueableDataUpdate only)
+ *   - e) At the completion of Maintenance::execute()
+ *
+ * @see Maintenance::setLBFactoryTriggers
  *
  * When updates are deferred, they go into one two FIFO "top-queues" (one for pre-send and one
  * for post-send). Updates enqueued *during* doUpdate() of a "top" update go into the "sub-queue"
@@ -206,23 +209,29 @@ class DeferredUpdates {
                        foreach ( $updatesByType as $updatesForType ) {
                                foreach ( $updatesForType as $update ) {
                                        self::$executeContext = [ 'stage' => $stage, 'subqueue' => [] ];
-                                       /** @var DeferrableUpdate $update */
-                                       $guiError = self::runUpdate( $update, $lbFactory, $mode, $stage );
-                                       $reportableError = $reportableError ?: $guiError;
-                                       // Do the subqueue updates for $update until there are none
-                                       while ( self::$executeContext['subqueue'] ) {
-                                               $subUpdate = reset( self::$executeContext['subqueue'] );
-                                               $firstKey = key( self::$executeContext['subqueue'] );
-                                               unset( self::$executeContext['subqueue'][$firstKey] );
-
-                                               if ( $subUpdate instanceof DataUpdate ) {
-                                                       $subUpdate->setTransactionTicket( $ticket );
-                                               }
-
-                                               $guiError = self::runUpdate( $subUpdate, $lbFactory, $mode, $stage );
+                                       try {
+                                               /** @var DeferrableUpdate $update */
+                                               $guiError = self::runUpdate( $update, $lbFactory, $mode, $stage );
                                                $reportableError = $reportableError ?: $guiError;
+                                               // Do the subqueue updates for $update until there are none
+                                               while ( self::$executeContext['subqueue'] ) {
+                                                       $subUpdate = reset( self::$executeContext['subqueue'] );
+                                                       $firstKey = key( self::$executeContext['subqueue'] );
+                                                       unset( self::$executeContext['subqueue'][$firstKey] );
+
+                                                       if ( $subUpdate instanceof DataUpdate ) {
+                                                               $subUpdate->setTransactionTicket( $ticket );
+                                                       }
+
+                                                       $guiError = self::runUpdate( $subUpdate, $lbFactory, $mode, $stage );
+                                                       $reportableError = $reportableError ?: $guiError;
+                                               }
+                                       } finally {
+                                               // Make sure we always clean up the context.
+                                               // Losing updates while rewinding the stack is acceptable,
+                                               // losing updates that are added later is not.
+                                               self::$executeContext = null;
                                        }
-                                       self::$executeContext = null;
                                }
                        }
 
@@ -265,6 +274,12 @@ class DeferredUpdates {
                                $guiError = $e;
                        }
                        MWExceptionHandler::rollbackMasterChangesAndLog( $e );
+
+                       // VW-style hack to work around T190178, so we can make sure
+                       // PageMetaDataUpdater doesn't throw exceptions.
+                       if ( defined( 'MW_PHPUNIT_TEST' ) ) {
+                               throw $e;
+                       }
                }
 
                return $guiError;
@@ -273,8 +288,9 @@ class DeferredUpdates {
        /**
         * Run all deferred updates immediately if there are no DB writes active
         *
-        * If $mode is 'run' but there are busy databates, EnqueueableDataUpdate
-        * tasks will be enqueued anyway for the sake of progress.
+        * If there are many deferred updates pending, $mode is 'run', and there
+        * are still busy LBFactory database handles, then any EnqueueableDataUpdate
+        * tasks might be enqueued as jobs to be executed later.
         *
         * @param string $mode Use "enqueue" to use the job queue when possible
         * @return bool Whether updates were allowed to run
@@ -361,7 +377,7 @@ class DeferredUpdates {
         */
        private static function areDatabaseTransactionsActive() {
                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
-               if ( $lbFactory->hasTransactionRound() ) {
+               if ( $lbFactory->hasTransactionRound() || !$lbFactory->isReadyForRoundOperations() ) {
                        return true;
                }
 
index 8913642..4ddd151 100644 (file)
@@ -177,15 +177,16 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate {
 
                // Commit and release the lock (if set)
                ScopedCallback::consume( $scopedLock );
-               // Run post-commit hooks without DBO_TRX
-               $this->getDB()->onTransactionIdle(
+               // Run post-commit hook handlers without DBO_TRX
+               DeferredUpdates::addUpdate( new AutoCommitUpdate(
+                       $this->getDB(),
+                       __METHOD__,
                        function () {
                                // Avoid PHP 7.1 warning from passing $this by reference
                                $linksUpdate = $this;
                                Hooks::run( 'LinksUpdateComplete', [ &$linksUpdate, $this->ticket ] );
-                       },
-                       __METHOD__
-               );
+                       }
+               ) );
        }
 
        /**
diff --git a/includes/deferred/SqlDataUpdate.php b/includes/deferred/SqlDataUpdate.php
deleted file mode 100644 (file)
index 2411bef..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-/**
- * Base code for update jobs that put some secondary data extracted
- * from article content into the database.
- *
- * 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
- */
-
-use Wikimedia\Rdbms\IDatabase;
-
-/**
- * @deprecated Since 1.28 Use DataUpdate directly, injecting the database
- */
-abstract class SqlDataUpdate extends DataUpdate {
-       /** @var IDatabase Database connection reference */
-       protected $mDb;
-       /** @var array SELECT options to be used (array) */
-       protected $mOptions = [];
-
-       public function __construct() {
-               parent::__construct();
-
-               $this->mDb = wfGetLB()->getLazyConnectionRef( DB_MASTER );
-       }
-}
index 53378e5..273d1d6 100644 (file)
@@ -24,6 +24,9 @@
  */
 use MediaWiki\Diff\ComplexityException;
 
+// FIXME: Don't use assert() in this file
+// phpcs:disable MediaWiki.Usage.ForbiddenFunctions.assert
+
 /**
  * This diff implementation is mainly lifted from the LCS algorithm of the Eclipse project which
  * in turn is based on Myers' "An O(ND) difference algorithm and its variations"
index ad80275..88631ed 100644 (file)
@@ -89,6 +89,8 @@ class WordAccumulator {
                                $this->flushLine( $tag );
                                $word = substr( $word, 1 );
                        }
+                       // FIXME: Don't use assert()
+                       // phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.assert
                        assert( !strstr( $word, "\n" ) );
                        $this->group .= $word;
                }
index 28021ef..b15f81f 100644 (file)
@@ -1628,7 +1628,11 @@ class FileRepo {
                $status = $this->newGood();
                $status->merge( $this->backend->streamFile( $params ) );
 
-               ob_end_flush();
+               // T186565: Close the buffer, unless it has already been closed
+               // in HTTPFileStreamer::resetOutputBuffers().
+               if ( ob_get_status() ) {
+                       ob_end_flush();
+               }
 
                return $status;
        }
index 06b21a8..cba21c8 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
 
 /**
  * A foreign repository with a remote MediaWiki with an API thingy
@@ -332,7 +333,7 @@ class ForeignAPIRepo extends FileRepo {
         * @return bool|string
         */
        function getThumbUrlFromCache( $name, $width, $height, $params = "" ) {
-               $cache = ObjectCache::getMainWANInstance();
+               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
                // We can't check the local cache using FileRepo functions because
                // we override fileExistsBatch(). We have to use the FileBackend directly.
                $backend = $this->getBackend(); // convenience
@@ -569,7 +570,7 @@ class ForeignAPIRepo extends FileRepo {
                        $url = $this->makeUrl( $query, 'api' );
                }
 
-               $cache = ObjectCache::getMainWANInstance();
+               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
                return $cache->getWithSetCallback(
                        $this->getLocalCacheKey( static::class, $target, md5( $url ) ),
                        $cacheTTL,
index 249cd27..302b194 100644 (file)
  * @ingroup FileRepo
  */
 
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\LoadBalancer;
+
 /**
  * A foreign repository with a MediaWiki database accessible via the configured LBFactory
  *
@@ -59,14 +63,14 @@ class ForeignDBViaLBRepo extends LocalRepo {
         * @return IDatabase
         */
        function getMasterDB() {
-               return wfGetLB( $this->wiki )->getConnectionRef( DB_MASTER, [], $this->wiki );
+               return $this->getDBLoadBalancer()->getConnectionRef( DB_MASTER, [], $this->wiki );
        }
 
        /**
         * @return IDatabase
         */
        function getReplicaDB() {
-               return wfGetLB( $this->wiki )->getConnectionRef( DB_REPLICA, [], $this->wiki );
+               return $this->getDBLoadBalancer()->getConnectionRef( DB_REPLICA, [], $this->wiki );
        }
 
        /**
@@ -74,10 +78,18 @@ class ForeignDBViaLBRepo extends LocalRepo {
         */
        protected function getDBFactory() {
                return function ( $index ) {
-                       return wfGetLB( $this->wiki )->getConnectionRef( $index, [], $this->wiki );
+                       return $this->getDBLoadBalancer()->getConnectionRef( $index, [], $this->wiki );
                };
        }
 
+       /**
+        * @return LoadBalancer
+        */
+       protected function getDBLoadBalancer() {
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               return $lbFactory->getMainLB( $this->wiki );
+       }
+
        function hasSharedCache() {
                return $this->hasSharedCache;
        }
index 76043d5..03a9d44 100644 (file)
@@ -22,6 +22,7 @@
  * @ingroup FileRepo
  */
 
+use MediaWiki\MediaWikiServices;
 use Wikimedia\Rdbms\ResultWrapper;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
@@ -200,7 +201,7 @@ class LocalRepo extends FileRepo {
                }
 
                $method = __METHOD__;
-               $redirDbKey = ObjectCache::getMainWANInstance()->getWithSetCallback(
+               $redirDbKey = MediaWikiServices::getInstance()->getMainWANObjectCache()->getWithSetCallback(
                        $memcKey,
                        $expiry,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $method, $title ) {
@@ -520,7 +521,7 @@ class LocalRepo extends FileRepo {
                if ( $key ) {
                        $this->getMasterDB()->onTransactionPreCommitOrIdle(
                                function () use ( $key ) {
-                                       ObjectCache::getMainWANInstance()->delete( $key );
+                                       MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
                                },
                                __METHOD__
                        );
index d9763c6..65e4345 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup FileAbstraction
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Class representing a row of the 'filearchive' table
  *
@@ -251,7 +253,7 @@ class ArchivedFile {
                        'fa_deleted',
                        'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
                        'fa_sha1',
-               ] + CommentStore::getStore()->getFields( 'fa_description' );
+               ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'fa_description' );
        }
 
        /**
@@ -264,7 +266,7 @@ class ArchivedFile {
         *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
         */
        public static function getQueryInfo() {
-               $commentQuery = CommentStore::getStore()->getJoin( 'fa_description' );
+               $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin( 'fa_description' );
                $actorQuery = ActorMigration::newMigration()->getJoin( 'fa_user' );
                return [
                        'tables' => [ 'filearchive' ] + $commentQuery['tables'] + $actorQuery['tables'],
@@ -310,7 +312,7 @@ class ArchivedFile {
                $this->metadata = $row->fa_metadata;
                $this->mime = "$row->fa_major_mime/$row->fa_minor_mime";
                $this->media_type = $row->fa_media_type;
-               $this->description = CommentStore::getStore()
+               $this->description = MediaWikiServices::getInstance()->getCommentStore()
                        // Legacy because $row may have come from self::selectFields()
                        ->getCommentLegacy( wfGetDB( DB_REPLICA ), 'fa_description', $row )->text;
                $this->user = User::newFromAnyId( $row->fa_user, $row->fa_user_text, $row->fa_actor );
index cfd1cf2..7c87af3 100644 (file)
@@ -2065,7 +2065,7 @@ abstract class File implements IDBAccessObject {
 
                $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $lang->getCode() );
                if ( $renderUrl ) {
-                       $cache = ObjectCache::getMainWANInstance();
+                       $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
                        $key = $this->repo->getLocalCacheKey(
                                'RemoteFileDescription',
                                'url',
index be88b49..1002b82 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup FileAbstraction
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Foreign file accessible through api.php requests.
  * Very hacky and inefficient, do not use :D
@@ -33,7 +35,7 @@ class ForeignAPIFile extends File {
        /** @var array */
        private $mInfo = [];
 
-       protected $repoClass = ForeignApiRepo::class;
+       protected $repoClass = ForeignAPIRepo::class;
 
        /**
         * @param Title|string|bool $title
@@ -360,7 +362,7 @@ class ForeignAPIFile extends File {
                $url = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgContLang->getCode() );
                $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', md5( $url ) );
 
-               ObjectCache::getMainWANInstance()->delete( $key );
+               MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
        }
 
        /**
@@ -368,7 +370,7 @@ class ForeignAPIFile extends File {
         */
        function purgeThumbnails( $options = [] ) {
                $key = $this->repo->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $this->getName() );
-               ObjectCache::getMainWANInstance()->delete( $key );
+               MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
 
                $files = $this->getThumbnails();
                // Give media handler a chance to filter the purge list
index 388e950..05df45b 100644 (file)
@@ -21,6 +21,7 @@
  * @ingroup FileAbstraction
  */
 
+use MediaWiki\MediaWikiServices;
 use Wikimedia\Rdbms\DBUnexpectedError;
 
 /**
@@ -150,7 +151,7 @@ class ForeignDBFile extends LocalFile {
                        return false; // no description page
                }
 
-               $cache = ObjectCache::getMainWANInstance();
+               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
 
                return $cache->getWithSetCallback(
                        $this->repo->getLocalCacheKey(
index cff1044..c078e90 100644 (file)
@@ -226,7 +226,7 @@ class LocalFile extends File {
                        'img_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'img_actor' : null,
                        'img_timestamp',
                        'img_sha1',
-               ] + CommentStore::getStore()->getFields( 'img_description' );
+               ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'img_description' );
        }
 
        /**
@@ -241,7 +241,7 @@ class LocalFile extends File {
         *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
         */
        public static function getQueryInfo( array $options = [] ) {
-               $commentQuery = CommentStore::getStore()->getJoin( 'img_description' );
+               $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin( 'img_description' );
                $actorQuery = ActorMigration::newMigration()->getJoin( 'img_user' );
                $ret = [
                        'tables' => [ 'image' ] + $commentQuery['tables'] + $actorQuery['tables'],
@@ -323,7 +323,7 @@ class LocalFile extends File {
                        return;
                }
 
-               $cache = ObjectCache::getMainWANInstance();
+               $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
                $cachedValues = $cache->getWithSetCallback(
                        $key,
                        $cache::TTL_WEEK,
@@ -388,7 +388,7 @@ class LocalFile extends File {
 
                $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(
                        function () use ( $key ) {
-                               ObjectCache::getMainWANInstance()->delete( $key );
+                               MediaWikiServices::getInstance()->getMainWANObjectCache()->delete( $key );
                        },
                        __METHOD__
                );
@@ -579,7 +579,7 @@ class LocalFile extends File {
        function decodeRow( $row, $prefix = 'img_' ) {
                $decoded = $this->unprefixRow( $row, $prefix );
 
-               $decoded['description'] = CommentStore::getStore()
+               $decoded['description'] = MediaWikiServices::getInstance()->getCommentStore()
                        ->getComment( 'description', (object)$decoded )->text;
 
                $decoded['user'] = User::newFromAnyId(
@@ -1321,7 +1321,7 @@ class LocalFile extends File {
                        ) {
                                $props = $this->repo->getFileProps( $srcPath );
                        } else {
-                               $mwProps = new MWFileProps( MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer() );
+                               $mwProps = new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
                                $props = $mwProps->getPropsFromPath( $srcPath, true );
                        }
                }
@@ -1462,7 +1462,7 @@ class LocalFile extends File {
                # Test to see if the row exists using INSERT IGNORE
                # This avoids race conditions by locking the row until the commit, and also
                # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
-               $commentStore = CommentStore::getStore();
+               $commentStore = MediaWikiServices::getInstance()->getCommentStore();
                list( $commentFields, $commentCallback ) =
                        $commentStore->insertWithTempTable( $dbw, 'img_description', $comment );
                $actorMigration = ActorMigration::newMigration();
@@ -2470,7 +2470,7 @@ class LocalFileDeleteBatch {
                $now = time();
                $dbw = $this->file->repo->getMasterDB();
 
-               $commentStore = CommentStore::getStore();
+               $commentStore = MediaWikiServices::getInstance()->getCommentStore();
                $actorMigration = ActorMigration::newMigration();
 
                $encTimestamp = $dbw->addQuotes( $dbw->timestamp( $now ) );
@@ -2830,7 +2830,7 @@ class LocalFileRestoreBatch {
 
                $dbw = $this->file->repo->getMasterDB();
 
-               $commentStore = CommentStore::getStore();
+               $commentStore = MediaWikiServices::getInstance()->getCommentStore();
                $actorMigration = ActorMigration::newMigration();
 
                $status = $this->file->repo->newGood();
index 3a6b879..aa434d0 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup FileAbstraction
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Class to represent a file in the oldimage table
  *
@@ -140,7 +142,7 @@ class OldLocalFile extends LocalFile {
                        'oi_timestamp',
                        'oi_deleted',
                        'oi_sha1',
-               ] + CommentStore::getStore()->getFields( 'oi_description' );
+               ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'oi_description' );
        }
 
        /**
@@ -155,7 +157,7 @@ class OldLocalFile extends LocalFile {
         *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
         */
        public static function getQueryInfo( array $options = [] ) {
-               $commentQuery = CommentStore::getStore()->getJoin( 'oi_description' );
+               $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin( 'oi_description' );
                $actorQuery = ActorMigration::newMigration()->getJoin( 'oi_user' );
                $ret = [
                        'tables' => [ 'oldimage' ] + $commentQuery['tables'] + $actorQuery['tables'],
@@ -446,7 +448,8 @@ class OldLocalFile extends LocalFile {
                        return false;
                }
 
-               $commentFields = CommentStore::getStore()->insert( $dbw, 'oi_description', $comment );
+               $commentFields = MediaWikiServices::getInstance()->getCommentStore()
+                       ->insert( $dbw, 'oi_description', $comment );
                $actorFields = ActorMigration::newMigration()->getInsertValues( $dbw, 'oi_user', $user );
                $dbw->insert( 'oldimage',
                        [
index b68c7e3..dfe6a8a 100644 (file)
@@ -29,7 +29,7 @@ class HTMLExpiryField extends HTMLFormField {
         * Use whatever the relative field is as the standard HTML input.
         */
        public function getInputHTML( $value ) {
-               return $this->relativeField->getInputHtml( $value );
+               return $this->relativeField->getInputHTML( $value );
        }
 
        protected function shouldInfuseOOUI() {
index 3eb3f5d..602ddee 100644 (file)
@@ -25,6 +25,8 @@ class HTMLTitleTextField extends HTMLTextField {
                        'relative' => false,
                        'creatable' => false,
                        'exists' => false,
+                       // This overrides the default from HTMLFormField
+                       'required' => true,
                ];
 
                parent::__construct( $params );
@@ -34,8 +36,16 @@ class HTMLTitleTextField extends HTMLTextField {
                if ( $this->mParent->getMethod() === 'get' && $value === '' ) {
                        // If the form is a GET form and has no value, assume it hasn't been
                        // submitted yet, and skip validation
+                       // TODO This doesn't look right, we should be able to tell the difference
+                       // between "not submitted" (null) and "submitted but empty" (empty string).
                        return parent::validate( $value, $alldata );
                }
+
+               if ( !$this->mParams['required'] && $value === '' ) {
+                       // If this field is not required and the value is empty, that's okay, skip validation
+                       return parent::validate( $value, $alldata );
+               }
+
                try {
                        if ( !$this->mParams['relative'] ) {
                                $title = Title::newFromTextThrow( $value );
index 6a55d69..cb0092d 100644 (file)
@@ -130,9 +130,9 @@ class WebInstallerOutput {
                global $wgStyleDirectory;
 
                $moduleNames = [
-                       // See SkinTemplate::setupSkinUserCss
+                       // Based on Skin::getDefaultModules
                        'mediawiki.legacy.shared',
-                       // See Vector::setupSkinUserCss
+                       // Based on Vector::setupSkinUserCss
                        'mediawiki.skinning.interface',
                ];
 
index ad2b910..8ab7abe 100644 (file)
        "config-nofile": "Soubor „$1“ nelze nalézt. Byl smazán?",
        "config-extension-link": "Věděli jste, že vaše wiki podporuje [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions rozšíření]?\n\nMůžete si prohlédnout [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category seznam rozšíření po kategoriích].",
        "config-skins-screenshots": "$1 (snímky obrazovky: $2)",
+       "config-extensions-requires": "$1 (vyžaduje $2)",
        "config-screenshot": "snímek obrazovky",
        "mainpagetext": "<strong>MediaWiki byla úspěšně nainstalována.</strong>",
        "mainpagedocfooter": "[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Uživatelská příručka] vám napoví, jak používat MediaWiki.\n\n== Začínáme ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Nastavení konfigurace]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Často kladené otázky o MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-mailová konference oznámení MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Překlad MediaWiki do vašeho jazyka]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Naučte se bojovat se spamem na vaší wiki]"
index e94c71b..18c917e 100644 (file)
@@ -64,7 +64,7 @@
        "config-apc": "[http://www.php.net/apc APC] instalatuta dago",
        "config-apcu": "[http://www.php.net/apcu APCu] instalatuta dago",
        "config-wincache": "[https://www.iis.net/download/WinCacheForPhp WinCache] instalatuta dago",
-       "config-no-cache-apcu": "<strong>Warning:</strong> Ezin izan da [http://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] edo [http://www.iis.net/download/WinCacheForPhp WinCache] aurkitu.\nObjektu katxea ez dago aktibatuta.",
+       "config-no-cache-apcu": "<strong>Warning:</strong> Ezin izan da [http://www.php.net/apcu APCu] edo [http://www.iis.net/download/WinCacheForPhp WinCache] aurkitu.\nObjektu katxea ez dago aktibatuta.",
        "config-mod-security": "<strong>Warning:</strong> Zure web zerbitzariak [https://modsecurity.org/mod_security] / mod_security2 aktibatu du. Honen konfigurazio komun asko sortu ahal dituzte arazoak MediaWikin  eta beste software batzuetan, hautazko edukia argitaratzeko aukera ematen dutenei erabiltzaileei.\nAhal izanez gero, desgaitu egin beharko litzateke. Bestela, kontsultatu [https://modsecurity.org/documentation/ mod_security documentation] edo jarri harremanetan zure ostalariarekin ausazko akatsak aurkitzen badituzu.",
        "config-diff3-bad": "GNU diff3 ez da aurkitu.",
        "config-git": "Git bertsio-kontrol software aurkitu da: <code>$1</code>",
        "config-cache-options": "Objektu cachearen ezarpenak:",
        "config-cache-help": "Objektuen katxea erabiltzen da MediaWikiko abiadura hobetzeko, sarritan erabiltzen diren datuak gordetzen.\nOso gomendagarria da, webgune handientzako eta ertainentzako, webgune txikiek ere ikusiko dituzte onurak.",
        "config-cache-none": "Desaktibatu Katxina (ez dira funtzionaltasunak ezabatu, baina wiki orrialde handietan abiaduran eragina izan ahal du)",
-       "config-cache-accel": "PHP objetuen katxea (APC, APCu, XCache edo WinCache)",
+       "config-cache-accel": "PHP objetuen katxea (APC, APCu, edo WinCache)",
        "config-cache-memcached": "Memcached erabili (konfigurazio eta instalazio gehiago behar du)",
        "config-memcached-servers": "Memcached serbidoreak:",
        "config-memcached-help": "Memcached-ekin erabiltzeko IP helbideen lista.\nLerro bakoitzen bat bakarrik jarri behar da eta zehaztu ze ataka erabiliko den. Adibidez:\n127.0.0.1:11211\n192.168.1.25:1234",
        "config-nofile": "Ezin da \"$1\" fitxategia aurkitu. Ezabatua izan da?",
        "config-extension-link": "Ba al zenekien wikiak [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensions] onartzen dituela?\n\nArakatu [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensions by category] edo [https://www.mediawiki.org/wiki/Extension_Matrix Extension Matrix] ikusi ahal izateko luzapenen zerrenda.",
        "config-skins-screenshots": "$1 (Pantaila-irudia: $2)",
+       "config-extensions-requires": "$1 ($2 behar du)",
        "config-screenshot": "Pantaila-irudia",
        "mainpagetext": "<strong>MediaWiki instalatu da.</strong>",
        "mainpagedocfooter": "Ikusi [https://meta.wikimedia.org/wiki/Help:Contents Erabiltzailearen Gida] wiki softwarea erabiltzen hasteko informazio gehiagorako.\n\n== Nola hasi ==\n\n*\n [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Konfigurazio balioen zerrenda]\n*\n [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ (MediaWikin Maiz egindako galderak)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWikiren argitalpenen posta zerrenda]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Aurkitu MediaWiki zure hizkuntzan]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Zure wikian spam-a nola borrokatzen ikasi]"
index c0b19c0..9bb5a85 100644 (file)
        "config-nofile": "Tiedostoa \"$1\" ei löytynyt. Onko se poistettu?",
        "config-extension-link": "Tiesitkö että wiki tukee [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions laajennuksia]?\n\nLaajennuksia voi hakea myös [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category luokittain].",
        "config-skins-screenshots": "$1 (kuvakaappaukset: $2)",
+       "config-extensions-requires": "$1 (vaatii $2)",
        "config-screenshot": "kuvakaappaus",
        "mainpagetext": "<strong>MediaWiki on onnistuneesti asennettu.</strong>",
        "mainpagedocfooter": "Lisätietoja wiki-ohjelmiston käytöstä on [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents käyttöoppaassa].\n\n=== Aloittaminen ===\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Asetusten teko-ohjeita]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWikin FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Sähköpostilista, jolla tiedotetaan MediaWikin uusista versioista]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Käännä MediaWikiä kielellesi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Katso, kuinka torjua spämmiä wikissäsi]\n\n=== Asetukset ===\n\nTarkista, että alla olevat taivutusmuodot ovat oikein. Jos eivät, tee tarvittavat muutokset tiedostoon LocalSettings.php seuraavasti:\n $wgGrammarForms['fi']['genitive']['{{SITENAME}}'] = '...';\n $wgGrammarForms['fi']['partitive']['{{SITENAME}}'] = '...';\n $wgGrammarForms['fi']['elative']['{{SITENAME}}'] = '...';\n $wgGrammarForms['fi']['inessive']['{{SITENAME}}'] = '...';\n $wgGrammarForms['fi']['illative']['{{SITENAME}}'] = '...';\nTaivutusmuodot: {{GRAMMAR:genitive|{{SITENAME}}}} (yön) – {{GRAMMAR:partitive|{{SITENAME}}}} (yötä) – {{GRAMMAR:elative|{{SITENAME}}}} (yöstä) – {{GRAMMAR:inessive|{{SITENAME}}}} (yössä) – {{GRAMMAR:illative|{{SITENAME}}}} (yöhön)."
index d9b0dbc..b9f0482 100644 (file)
@@ -20,7 +20,8 @@
                        "Matteocng",
                        "Einreiher",
                        "Tosky",
-                       "Selven"
+                       "Selven",
+                       "Sarah Bernabei"
                ]
        },
        "config-desc": "Programma di installazione per MediaWiki",
        "config-help-tooltip": "fai clic per espandere",
        "config-nofile": "Il file \"$1\" non può essere trovato. È stato eliminato?",
        "config-extension-link": "Sapevi che il tuo wiki supporta le  [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions estensioni]?\n\nPuoi navigare tra le [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category estensioni per categoria].",
+       "config-extensions-requires": "$1 (richiesto $2)",
        "mainpagetext": "<strong>MediaWiki è stato installato.</strong>",
        "mainpagedocfooter": "Consulta la [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents guida utente] per maggiori informazioni sull'uso di questo software wiki.\n\n== Per iniziare ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Impostazioni di configurazione]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Domande frequenti su MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list annunci MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Trova MediaWiki nella tua lingua]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Imparare a combattere lo spam sul tuo wiki]"
 }
index c54dd81..a784813 100644 (file)
        "config-nofile": "ファイル「$1」が見つかりませんでした。削除された可能性があります。",
        "config-extension-link": "あなたのウィキは[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions 拡張機能]をサポートしていることをご存知ですか?\n\n[https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category カテゴリ別で拡張機能を見る]か[https://www.mediawiki.org/wiki/Extension_Matrix 拡張機能のマトリックス]で拡張機能すべてのリストをご覧になれます。",
        "config-skins-screenshots": "$1 (スクリーンショット: $2)",
+       "config-extensions-requires": "$1($2が必要)",
        "config-screenshot": "スクリーンショット",
        "mainpagetext": "<strong>MediaWiki はインストール済みです。</strong>",
        "mainpagedocfooter": "ウィキソフトウェアの使い方に関する情報は[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 利用者案内]を参照してください。\n\n== はじめましょう ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings/ja 設定の一覧]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/ja MediaWiki よくある質問と回答]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki リリース情報メーリングリスト]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation/ja MediaWiki のあなたの言語へのローカライズ]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam あなたのウィキでスパムと戦う方法を学ぶ]"
index 66fb4ca..cc42c42 100644 (file)
        "config-help": "Hëllef",
        "config-help-tooltip": "klickt fir opzeklappen",
        "config-nofile": "De Fichier \"$1\" gouf net fonnt. Gouf e geläscht?",
+       "config-extensions-requires": "$1 (brauch $2)",
        "config-screenshot": "Screenshot",
        "mainpagetext": "<strong>MediaWiki gouf installéiert.</strong>",
        "mainpagedocfooter": "Kuckt w.e.g. [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents d'Benotzerhandbuch] fir Informatiounen iwwer de Gebruach vun der Wiki Software.\n\n== Fir  unzefänken ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Hëllef bei der Konfiguratioun]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki-FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglëscht vun neie MediaWiki-Versiounen]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Lokaliséiert MediaWiki fir Är Sprooch]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Léiert wéi Spam op Ärer Wiki reduzéiert gi kann]"
index 55ce440..4f158f9 100644 (file)
@@ -56,6 +56,7 @@
        "config-connection-error": "$1.\n\nതാഴെ നൽകിയിരിക്കുന്ന ഹോസ്റ്റ്, ഉപയോക്തൃനാമം, രഹസ്യവാക്ക് എന്നിവ പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക.",
        "config-regenerate": "LocalSettings.php പുനഃസൃഷ്ടിക്കുക →",
        "config-mysql-engine": "സ്റ്റോറേജ് എൻജിൻ:",
+       "config-mysql-utf8": "യു.ടി.എഫ്.-8",
        "config-site-name": "വിക്കിയുടെ പേര്:",
        "config-site-name-help": "ഇത് ബ്രൗസറിന്റെ ടൈറ്റിൽ ബാറിലും മറ്റനേകം ഇടങ്ങളിലും പ്രദർശിപ്പിക്കപ്പെടും.",
        "config-site-name-blank": "സൈറ്റിന്റെ പേര് നൽകുക.",
index 276d8a9..c00cf18 100644 (file)
        "config-nofile": "Filen \"$1\" ble ikke funnet. Kan den være blitt slettet?",
        "config-extension-link": "Visste du at wikien din kan brukes sammen med en mengde [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions utvidelser]?\n\nDu kan sjekke gjennom [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category utvidelser per kategori] eller [https://www.mediawiki.org/wiki/Extension_Matrix utvidelsesmatrisen] for å se den komplette listen av utvidelser.",
        "config-skins-screenshots": "$1 (skjermbilder: $2)",
+       "config-extensions-requires": "$1 (krever $2)",
        "config-screenshot": "skjermbilde",
        "mainpagetext": "<strong>MediaWiki har blitt installert.</strong>",
        "mainpagedocfooter": "Sjekk [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents brukerveiledningen] for å få informasjon om hvordan du bruker wiki-programvaren.\n\n==Hvordan komme igang==\n*[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Innstillingsliste]\n*[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Ofte stilte spørsmål om MediaWiki]\n*[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-postliste]\n*[https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Tilpass MediaWiki for ditt språk]\n*[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Lær deg å beskytte deg mot spam på wikien din]"
index dfffd68..f64add1 100644 (file)
@@ -1,8 +1,12 @@
 {
-    "@metadata": {
-        "authors": [
-            "Mark85296341"
-        ]
-    },
-    "mainpagedocfooter": "請參閱[https://meta.wikimedia.org/wiki/Help:Contents 用戶手冊]以獲得使用此 wiki 軟件的訊息!\n\n== 入門 ==\n* [https://www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置設定清單]\n* [https://www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常見問題解答]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件清單]"
+       "@metadata": {
+               "authors": [
+                       "Mark85296341",
+                       "Liuxinyu970226"
+               ]
+       },
+       "config-license-cc-by-sa": "共享創意姓名標示-相同方式分享",
+       "config-license-cc-by": "共享創意姓名標示",
+       "config-cc-not-chosen": "請選擇您要使用的共享創意授權條款,然後點選 \"proceed\"。",
+       "mainpagedocfooter": "請參閱[https://meta.wikimedia.org/wiki/Help:Contents 用戶手冊]以獲得使用此 wiki 軟件的訊息!\n\n== 入門 ==\n* [https://www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置設定清單]\n* [https://www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常見問題解答]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件清單]"
 }
index 5a996d9..657849a 100644 (file)
@@ -36,7 +36,7 @@ class Interwiki {
        protected $mAPI;
 
        /** @var string The name of the database (for a connection to be established
-        *    with wfGetLB( 'wikiid' ))
+        *    with LBFactory::getMainLB( 'wikiid' ))
         */
        protected $mWikiID;
 
index 9f8959c..46cd6be 100644 (file)
@@ -64,7 +64,6 @@ class HTTPFileStreamer {
         * @param bool $sendErrors Send error messages if errors occur (like 404)
         * @param array $optHeaders HTTP request header map (e.g. "range") (use lowercase keys)
         * @param int $flags Bitfield of STREAM_* constants
-        * @throws MWException
         * @return bool Success
         */
        public function stream(
index e115286..90e697e 100644 (file)
@@ -212,10 +212,10 @@ class ChronologyProtector implements LoggerAwareInterface {
                        $store->unlock( $this->key );
                } else {
                        $ok = false;
-                       $cpIndex = null; // nothing saved
                }
 
                if ( !$ok ) {
+                       $cpIndex = null; // nothing saved
                        $bouncedPositions = $this->shutdownPositions;
                        // Raced out too many times or stash is down
                        $this->logger->warning( __METHOD__ . ": failed to save master pos for " .
@@ -269,14 +269,16 @@ class ChronologyProtector implements LoggerAwareInterface {
                        // already be expired and thus treated as non-existing, maintaining correctness.
                        if ( $this->waitForPosIndex > 0 ) {
                                $data = null;
+                               $indexReached = null; // highest index reached in the position store
                                $loop = new WaitConditionLoop(
-                                       function () use ( &$data ) {
+                                       function () use ( &$data, &$indexReached ) {
                                                $data = $this->store->get( $this->key );
                                                if ( !is_array( $data ) ) {
                                                        return WaitConditionLoop::CONDITION_CONTINUE; // not found yet
                                                } elseif ( !isset( $data['writeIndex'] ) ) {
                                                        return WaitConditionLoop::CONDITION_REACHED; // b/c
                                                }
+                                               $indexReached = max( $data['writeIndex'], $indexReached );
 
                                                return ( $data['writeIndex'] >= $this->waitForPosIndex )
                                                        ? WaitConditionLoop::CONDITION_REACHED
@@ -288,11 +290,22 @@ class ChronologyProtector implements LoggerAwareInterface {
                                $waitedMs = $loop->getLastWaitTime() * 1e3;
 
                                if ( $result == $loop::CONDITION_REACHED ) {
-                                       $msg = "expected and found pos index {$this->waitForPosIndex} ({$waitedMs}ms)";
-                                       $this->logger->debug( $msg );
+                                       $this->logger->debug(
+                                               __METHOD__ . ": expected and found position index.",
+                                               [
+                                                       'cpPosIndex' => $this->waitForPosIndex,
+                                                       'waitTimeMs' => $waitedMs
+                                               ]
+                                       );
                                } else {
-                                       $msg = "expected but missed pos index {$this->waitForPosIndex} ({$waitedMs}ms)";
-                                       $this->logger->info( $msg );
+                                       $this->logger->warning(
+                                               __METHOD__ . ": expected but failed to find position index.",
+                                               [
+                                                       'cpPosIndex' => $this->waitForPosIndex,
+                                                       'indexReached' => $indexReached,
+                                                       'waitTimeMs' => $waitedMs
+                                               ]
+                                       );
                                }
                        } else {
                                $data = $this->store->get( $this->key );
index c94f62f..3432bff 100644 (file)
@@ -113,6 +113,10 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
+       public function preCommitCallbacksPending() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
        public function writesOrCallbacksPending() {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
index 189eaf2..1517bd9 100644 (file)
@@ -677,6 +677,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                );
        }
 
+       public function preCommitCallbacksPending() {
+               return $this->trxLevel && $this->trxPreCommitCallbacks;
+       }
+
        /**
         * @return string|null
         */
@@ -722,17 +726,15 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        /**
-        * Get the list of method names that have pending write queries or callbacks
-        * for this transaction
+        * List the methods that have write queries or callbacks for the current transaction
         *
-        * @return array
+        * This method should not be used outside of Database/LoadBalancer
+        *
+        * @return string[]
+        * @since 1.32
         */
-       protected function pendingWriteAndCallbackCallers() {
-               if ( !$this->trxLevel ) {
-                       return [];
-               }
-
-               $fnames = $this->trxWriteCallers;
+       public function pendingWriteAndCallbackCallers() {
+               $fnames = $this->pendingWriteCallers();
                foreach ( [
                        $this->trxIdleCallbacks,
                        $this->trxPreCommitCallbacks,
@@ -960,12 +962,10 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                }
 
                // Sanity check that no callbacks are dangling
-               if (
-                       $this->trxIdleCallbacks || $this->trxPreCommitCallbacks || $this->trxEndCallbacks
-               ) {
+               $fnames = $this->pendingWriteAndCallbackCallers();
+               if ( $fnames ) {
                        throw new RuntimeException(
-                               "Transaction callbacks are still pending:\n" .
-                               implode( ', ', $this->pendingWriteAndCallbackCallers() )
+                               "Transaction callbacks are still pending:\n" . implode( ', ', $fnames )
                        );
                }
 
@@ -991,17 +991,13 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        abstract protected function closeConnection();
 
        /**
-        * @param string $error Fallback error message, used if none is given by DB
+        * @deprecated since 1.32
+        * @param string $error Fallback message, if none is given by DB
         * @throws DBConnectionError
         */
        public function reportConnectionError( $error = 'Unknown error' ) {
-               $myError = $this->lastError();
-               if ( $myError ) {
-                       $error = $myError;
-               }
-
-               # New method
-               throw new DBConnectionError( $this, $error );
+               call_user_func( $this->deprecationLogger, 'Use of ' . __METHOD__ . ' is deprecated.' );
+               throw new DBConnectionError( $this, $this->lastError() ?: $error );
        }
 
        /**
@@ -3418,19 +3414,25 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        }
 
        /**
-        * Actually run and consume any "on transaction idle/resolution" callbacks.
+        * Actually consume and run any "on transaction idle/resolution" callbacks.
         *
         * This method should not be used outside of Database/LoadBalancer
         *
         * @param int $trigger IDatabase::TRIGGER_* constant
+        * @return int Number of callbacks attempted
         * @since 1.20
         * @throws Exception
         */
        public function runOnTransactionIdleCallbacks( $trigger ) {
+               if ( $this->trxLevel ) { // sanity
+                       throw new DBUnexpectedError( $this, __METHOD__ . ': a transaction is still open.' );
+               }
+
                if ( $this->trxEndCallbacksSuppressed ) {
-                       return;
+                       return 0;
                }
 
+               $count = 0;
                $autoTrx = $this->getFlag( self::DBO_TRX ); // automatic begin() enabled?
                /** @var Exception $e */
                $e = null; // first exception
@@ -3443,6 +3445,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $this->trxEndCallbacks = []; // consumed (recursion guard)
                        foreach ( $callbacks as $callback ) {
                                try {
+                                       ++$count;
                                        list( $phpCallback ) = $callback;
                                        $this->clearFlag( self::DBO_TRX ); // make each query its own transaction
                                        call_user_func( $phpCallback, $trigger, $this );
@@ -3466,23 +3469,29 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                if ( $e instanceof Exception ) {
                        throw $e; // re-throw any first exception
                }
+
+               return $count;
        }
 
        /**
-        * Actually run and consume any "on transaction pre-commit" callbacks.
+        * Actually consume and run any "on transaction pre-commit" callbacks.
         *
         * This method should not be used outside of Database/LoadBalancer
         *
         * @since 1.22
+        * @return int Number of callbacks attempted
         * @throws Exception
         */
        public function runOnTransactionPreCommitCallbacks() {
+               $count = 0;
+
                $e = null; // first exception
                do { // callbacks may add callbacks :)
                        $callbacks = $this->trxPreCommitCallbacks;
                        $this->trxPreCommitCallbacks = []; // consumed (and recursion guard)
                        foreach ( $callbacks as $callback ) {
                                try {
+                                       ++$count;
                                        list( $phpCallback ) = $callback;
                                        call_user_func( $phpCallback, $this );
                                } catch ( Exception $ex ) {
@@ -3495,6 +3504,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                if ( $e instanceof Exception ) {
                        throw $e; // re-throw any first exception
                }
+
+               return $count;
        }
 
        /**
@@ -3595,7 +3606,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $savepointId = $cancelable === self::ATOMIC_CANCELABLE ? self::$NOT_APPLICABLE : null;
 
                if ( !$this->trxLevel ) {
-                       $this->begin( $fname, self::TRANSACTION_INTERNAL );
+                       $this->begin( $fname, self::TRANSACTION_INTERNAL ); // sets trxAutomatic
                        // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
                        // in all changes being in one transaction to keep requests transactional.
                        if ( $this->getFlag( self::DBO_TRX ) ) {
@@ -3849,8 +3860,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        );
                }
 
-               $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
-               $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT );
+               // With FLUSHING_ALL_PEERS, callbacks will be explicitly run later
+               if ( $flush !== self::FLUSHING_ALL_PEERS ) {
+                       $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
+                       $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT );
+               }
        }
 
        /**
@@ -3899,7 +3913,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $this->trxIdleCallbacks = [];
                $this->trxPreCommitCallbacks = [];
 
-               if ( $trxActive ) {
+               // With FLUSHING_ALL_PEERS, callbacks will be explicitly run later
+               if ( $trxActive && $flush !== self::FLUSHING_ALL_PEERS ) {
                        try {
                                $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
                        } catch ( Exception $e ) {
index 3e6190c..0472139 100644 (file)
@@ -152,9 +152,7 @@ abstract class DatabaseMysqlBase extends Database {
 
                # Always log connection errors
                if ( !$this->conn ) {
-                       if ( !$error ) {
-                               $error = $this->lastError();
-                       }
+                       $error = $error ?: $this->lastError();
                        $this->connLogger->error(
                                "Error connecting to {db_server}: {error}",
                                $this->getLogContext( [
@@ -166,7 +164,7 @@ abstract class DatabaseMysqlBase extends Database {
                                "Server: $server, User: $user, Password: " .
                                substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
 
-                       $this->reportConnectionError( $error );
+                       throw new DBConnectionError( $this, $error );
                }
 
                if ( strlen( $dbName ) ) {
@@ -174,22 +172,29 @@ abstract class DatabaseMysqlBase extends Database {
                        $success = $this->selectDB( $dbName );
                        Wikimedia\restoreWarnings();
                        if ( !$success ) {
+                               $error = $this->lastError();
                                $this->queryLogger->error(
-                                       "Error selecting database {db_name} on server {db_server}",
+                                       "Error selecting database {db_name} on server {db_server}: {error}",
                                        $this->getLogContext( [
                                                'method' => __METHOD__,
+                                               'error' => $error,
                                        ] )
                                );
-                               $this->queryLogger->debug(
-                                       "Error selecting database $dbName on server {$this->server}" );
-
-                               $this->reportConnectionError( "Error selecting database $dbName" );
+                               throw new DBConnectionError( $this, "Error selecting database $dbName: $error" );
                        }
                }
 
                // Tell the server what we're communicating with
                if ( !$this->connectInitCharset() ) {
-                       $this->reportConnectionError( "Error setting character set" );
+                       $error = $this->lastError();
+                       $this->queryLogger->error(
+                               "Error setting character set: {error}",
+                               $this->getLogContext( [
+                                       'method' => __METHOD__,
+                                       'error' => $this->lastError(),
+                               ] )
+                       );
+                       throw new DBConnectionError( $this, "Error setting character set: $error" );
                }
 
                // Abstract over any insane MySQL defaults
@@ -212,14 +217,15 @@ abstract class DatabaseMysqlBase extends Database {
                        // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
                        $success = $this->doQuery( 'SET ' . implode( ', ', $set ) );
                        if ( !$success ) {
+                               $error = $this->lastError();
                                $this->queryLogger->error(
-                                       'Error setting MySQL variables on server {db_server} (check $wgSQLMode)',
+                                       'Error setting MySQL variables on server {db_server}: {error}',
                                        $this->getLogContext( [
                                                'method' => __METHOD__,
+                                               'error' => $error,
                                        ] )
                                );
-                               $this->reportConnectionError(
-                                       'Error setting MySQL variables on server {db_server} (check $wgSQLMode)' );
+                               throw new DBConnectionError( $this, "Error setting MySQL variables: $error" );
                        }
                }
 
index 6b3efa2..43e9751 100644 (file)
@@ -58,7 +58,7 @@ interface IDatabase {
        /** @var string Commit/rollback is from the connection manager for the IDatabase handle */
        const FLUSHING_ALL_PEERS = 'flush';
        /** @var string Commit/rollback is from the IDatabase handle internally */
-       const FLUSHING_INTERNAL = 'flush';
+       const FLUSHING_INTERNAL = 'flush-internal';
 
        /** @var string Do not remember the prior flags */
        const REMEMBER_NOTHING = '';
@@ -254,8 +254,15 @@ interface IDatabase {
        public function writesPending();
 
        /**
-        * Returns true if there is a transaction/round open with possible write
-        * queries or transaction pre-commit/idle callbacks waiting on it to finish.
+        * @return bool Whether there is a transaction open with pre-commit callbacks pending
+        * @since 1.32
+        */
+       public function preCommitCallbacksPending();
+
+       /**
+        * Whether there is a transaction open with either possible write queries
+        * or unresolved pre-commit/commit/resolution callbacks pending
+        *
         * This does *not* count recurring callbacks, e.g. from setTransactionListener().
         *
         * @return bool
@@ -1508,6 +1515,9 @@ interface IDatabase {
         * It can also be used for updates that easily suffer from lock timeouts and deadlocks,
         * but where atomicity is not essential.
         *
+        * Avoid using IDatabase instances aside from this one in the callback, unless such instances
+        * never have IDatabase::DBO_TRX set. This keeps callbacks from interfering with one another.
+        *
         * Updates will execute in the order they were enqueued.
         *
         * @note: do not assume that *other* IDatabase instances will be AUTOCOMMIT mode
@@ -1555,7 +1565,10 @@ interface IDatabase {
         *   - This IDatabase object
         * Callbacks must commit any transactions that they begin.
         *
-        * Registering a callback here will not affect writesOrCallbacks() pending
+        * Registering a callback here will not affect writesOrCallbacks() pending.
+        *
+        * Since callbacks from this method or onTransactionIdle() can start and end transactions,
+        * a single call to IDatabase::commit might trigger multiple runs of the listener callbacks.
         *
         * @param string $name Callback name
         * @param callable|null $callback Use null to unset a listener
index 1e8838e..45e7cbb 100644 (file)
@@ -195,12 +195,22 @@ interface ILBFactory {
        public function rollbackMasterChanges( $fname = __METHOD__ );
 
        /**
-        * Check if a transaction round is active
+        * Check if an explicit transaction round is active
         * @return bool
         * @since 1.29
         */
        public function hasTransactionRound();
 
+       /**
+        * Check if transaction rounds can be started, committed, or rolled back right now
+        *
+        * This can be used as a recusion guard to avoid exceptions in transaction callbacks
+        *
+        * @return bool
+        * @since 1.32
+        */
+       public function isReadyForRoundOperations();
+
        /**
         * Determine if any master connection has pending changes
         * @return bool
index c272147..fe18536 100644 (file)
@@ -88,6 +88,14 @@ abstract class LBFactory implements ILBFactory {
        /** @var string Agent name for query profiling */
        protected $agent;
 
+       /** @var string One of the ROUND_* class constants */
+       private $trxRoundStage = self::ROUND_CURSORY;
+
+       const ROUND_CURSORY = 'cursory';
+       const ROUND_BEGINNING = 'within-begin';
+       const ROUND_COMMITTING = 'within-commit';
+       const ROUND_ROLLING_BACK = 'within-rollback';
+
        private static $loggerFields =
                [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ];
 
@@ -206,12 +214,14 @@ abstract class LBFactory implements ILBFactory {
                $this->forEachLBCallMethod( 'flushReplicaSnapshots', [ $fname ] );
        }
 
-       public function commitAll( $fname = __METHOD__, array $options = [] ) {
+       final public function commitAll( $fname = __METHOD__, array $options = [] ) {
                $this->commitMasterChanges( $fname, $options );
                $this->forEachLBCallMethod( 'commitAll', [ $fname ] );
        }
 
-       public function beginMasterChanges( $fname = __METHOD__ ) {
+       final public function beginMasterChanges( $fname = __METHOD__ ) {
+               $this->assertTransactionRoundStage( self::ROUND_CURSORY );
+               $this->trxRoundStage = self::ROUND_BEGINNING;
                if ( $this->trxRoundId !== false ) {
                        throw new DBTransactionError(
                                null,
@@ -221,9 +231,12 @@ abstract class LBFactory implements ILBFactory {
                $this->trxRoundId = $fname;
                // Set DBO_TRX flags on all appropriate DBs
                $this->forEachLBCallMethod( 'beginMasterChanges', [ $fname ] );
+               $this->trxRoundStage = self::ROUND_CURSORY;
        }
 
-       public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
+       final public function commitMasterChanges( $fname = __METHOD__, array $options = [] ) {
+               $this->assertTransactionRoundStage( self::ROUND_CURSORY );
+               $this->trxRoundStage = self::ROUND_COMMITTING;
                if ( $this->trxRoundId !== false && $this->trxRoundId !== $fname ) {
                        throw new DBTransactionError(
                                null,
@@ -233,7 +246,12 @@ abstract class LBFactory implements ILBFactory {
                /** @noinspection PhpUnusedLocalVariableInspection */
                $scope = $this->getScopedPHPBehaviorForCommit(); // try to ignore client aborts
                // Run pre-commit callbacks and suppress post-commit callbacks, aborting on failure
-               $this->forEachLBCallMethod( 'finalizeMasterChanges' );
+               do {
+                       $count = 0; // number of callbacks executed this iteration
+                       $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$count ) {
+                               $count += $lb->finalizeMasterChanges();
+                       } );
+               } while ( $count > 0 );
                $this->trxRoundId = false;
                // Perform pre-commit checks, aborting on failure
                $this->forEachLBCallMethod( 'approveMasterChanges', [ $options ] );
@@ -241,35 +259,54 @@ abstract class LBFactory implements ILBFactory {
                $this->logIfMultiDbTransaction();
                // Actually perform the commit on all master DB connections and revert DBO_TRX
                $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
-               // Run all post-commit callbacks
-               /** @var Exception $e */
-               $e = null; // first callback exception
-               $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e ) {
-                       $ex = $lb->runMasterPostTrxCallbacks( IDatabase::TRIGGER_COMMIT );
-                       $e = $e ?: $ex;
-               } );
-               // Commit any dangling DBO_TRX transactions from callbacks on one DB to another DB
-               $this->forEachLBCallMethod( 'commitMasterChanges', [ $fname ] );
+               // Run all post-commit callbacks in a separate step
+               $e = $this->executePostTransactionCallbacks();
+               $this->trxRoundStage = self::ROUND_CURSORY;
                // Throw any last post-commit callback error
                if ( $e instanceof Exception ) {
                        throw $e;
                }
        }
 
-       public function rollbackMasterChanges( $fname = __METHOD__ ) {
+       final public function rollbackMasterChanges( $fname = __METHOD__ ) {
+               $this->trxRoundStage = self::ROUND_ROLLING_BACK;
                $this->trxRoundId = false;
-               $this->forEachLBCallMethod( 'suppressTransactionEndCallbacks' );
+               // Actually perform the rollback on all master DB connections and revert DBO_TRX
                $this->forEachLBCallMethod( 'rollbackMasterChanges', [ $fname ] );
-               // Run all post-rollback callbacks
-               $this->forEachLB( function ( ILoadBalancer $lb ) {
-                       $lb->runMasterPostTrxCallbacks( IDatabase::TRIGGER_ROLLBACK );
+               // Run all post-commit callbacks in a separate step
+               $this->executePostTransactionCallbacks();
+               $this->trxRoundStage = self::ROUND_CURSORY;
+       }
+
+       /**
+        * @return Exception|null
+        */
+       private function executePostTransactionCallbacks() {
+               // Run all post-commit callbacks until new ones stop getting added
+               $e = null; // first callback exception
+               do {
+                       $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e ) {
+                               $ex = $lb->runMasterTransactionIdleCallbacks();
+                               $e = $e ?: $ex;
+                       } );
+               } while ( $this->hasMasterChanges() );
+               // Run all listener callbacks once
+               $this->forEachLB( function ( ILoadBalancer $lb ) use ( &$e ) {
+                       $ex = $lb->runMasterTransactionListenerCallbacks();
+                       $e = $e ?: $ex;
                } );
+
+               return $e;
        }
 
        public function hasTransactionRound() {
                return ( $this->trxRoundId !== false );
        }
 
+       public function isReadyForRoundOperations() {
+               return ( $this->trxRoundStage === self::ROUND_CURSORY );
+       }
+
        /**
         * Log query info if multi DB transactions are going to be committed now
         */
@@ -408,7 +445,7 @@ abstract class LBFactory implements ILBFactory {
                return $this->ticket;
        }
 
-       public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
+       final public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
                if ( $ticket !== $this->ticket ) {
                        $this->perfLogger->error( __METHOD__ . ": $fname does not have outer scope.\n" .
                                ( new RuntimeException() )->getTraceAsString() );
@@ -597,6 +634,18 @@ abstract class LBFactory implements ILBFactory {
                $this->requestInfo = $info + $this->requestInfo;
        }
 
+       /**
+        * @param string $stage
+        */
+       private function assertTransactionRoundStage( $stage ) {
+               if ( $this->trxRoundStage !== $stage ) {
+                       throw new DBTransactionError(
+                               null,
+                               "Transaction round stage must be '$stage' (not '{$this->trxRoundStage}')"
+                       );
+               }
+       }
+
        /**
         * Make PHP ignore user aborts/disconnects until the returned
         * value leaves scope. This returns null and does nothing in CLI mode.
index fec496e..5d217e2 100644 (file)
@@ -377,10 +377,11 @@ interface ILoadBalancer {
        public function commitAll( $fname = __METHOD__ );
 
        /**
-        * Perform all pre-commit callbacks that remain part of the atomic transactions
-        * and disable any post-commit callbacks until runMasterPostTrxCallbacks()
+        * Run pre-commit callbacks and defer execution of post-commit callbacks
         *
         * Use this only for mutli-database commits
+        *
+        * @return int Number of pre-commit callbacks run (since 1.32)
         */
        public function finalizeMasterChanges();
 
@@ -417,14 +418,18 @@ interface ILoadBalancer {
        public function commitMasterChanges( $fname = __METHOD__ );
 
        /**
-        * Issue all pending post-COMMIT/ROLLBACK callbacks
+        * Consume and run all pending post-COMMIT/ROLLBACK callbacks and commit dangling transactions
         *
-        * Use this only for mutli-database commits
+        * @return Exception|null The first exception or null if there were none
+        */
+       public function runMasterTransactionIdleCallbacks();
+
+       /**
+        * Run all recurring post-COMMIT/ROLLBACK listener callbacks
         *
-        * @param int $type IDatabase::TRIGGER_* constant
         * @return Exception|null The first exception or null if there were none
         */
-       public function runMasterPostTrxCallbacks( $type );
+       public function runMasterTransactionListenerCallbacks();
 
        /**
         * Issue ROLLBACK only on master, only if queries were done on connection
@@ -433,15 +438,6 @@ interface ILoadBalancer {
         */
        public function rollbackMasterChanges( $fname = __METHOD__ );
 
-       /**
-        * Suppress all pending post-COMMIT/ROLLBACK callbacks
-        *
-        * Use this only for mutli-database commits
-        *
-        * @return Exception|null The first exception or null if there were none
-        */
-       public function suppressTransactionEndCallbacks();
-
        /**
         * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
         *
@@ -455,7 +451,7 @@ interface ILoadBalancer {
        public function hasMasterConnection();
 
        /**
-        * Determine if there are pending changes in a transaction by this thread
+        * Whether there are pending changes or callbacks in a transaction by this thread
         * @return bool
         */
        public function hasMasterChanges();
index f0175c9..cb6e4f4 100644 (file)
@@ -120,6 +120,8 @@ class LoadBalancer implements ILoadBalancer {
        private $connectionAttempted = false;
        /** @var int */
        private $maxLag = self::MAX_LAG_DEFAULT;
+       /** @var string Stage of the current transaction round in the transaction round life-cycle */
+       private $trxRoundStage = self::ROUND_CURSORY;
 
        /** @var int Warn when this many connection are held */
        const CONN_HELD_WARN_THRESHOLD = 10;
@@ -139,6 +141,19 @@ class LoadBalancer implements ILoadBalancer {
        const KEY_FOREIGN_FREE_NOROUND = 'foreignFreeAutoCommit';
        const KEY_FOREIGN_INUSE_NOROUND = 'foreignInUseAutoCommit';
 
+       /** @var string Transaction round, explicit or implicit, has not finished writing */
+       const ROUND_CURSORY = 'cursory';
+       /** @var string Transaction round writes are complete and ready for pre-commit checks */
+       const ROUND_FINALIZED = 'finalized';
+       /** @var string Transaction round passed final pre-commit checks */
+       const ROUND_APPROVED = 'approved';
+       /** @var string Transaction round was committed and post-commit callbacks must be run */
+       const ROUND_COMMIT_CALLBACKS = 'commit-callbacks';
+       /** @var string Transaction round was rolled back and post-rollback callbacks must be run */
+       const ROUND_ROLLBACK_CALLBACKS = 'rollback-callbacks';
+       /** @var string Transaction round encountered an error */
+       const ROUND_ERROR = 'error';
+
        public function __construct( array $params ) {
                if ( !isset( $params['servers'] ) ) {
                        throw new InvalidArgumentException( __CLASS__ . ': missing servers parameter' );
@@ -1130,8 +1145,7 @@ class LoadBalancer implements ILoadBalancer {
                                $context
                        );
 
-                       // throws DBConnectionError
-                       $conn->reportConnectionError( "{$this->lastError} ({$context['db_server']})" );
+                       throw new DBConnectionError( $conn, "{$this->lastError} ({$context['db_server']})" );
                } else {
                        // No last connection, probably due to all servers being too busy
                        $this->connLogger->error(
@@ -1243,44 +1257,41 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        public function commitAll( $fname = __METHOD__ ) {
-               $failures = [];
-
-               $restore = ( $this->trxRoundId !== false );
-               $this->trxRoundId = false;
-               $this->forEachOpenConnection(
-                       function ( IDatabase $conn ) use ( $fname, $restore, &$failures ) {
-                               try {
-                                       $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
-                               } catch ( DBError $e ) {
-                                       call_user_func( $this->errorLogger, $e );
-                                       $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
-                               }
-                               if ( $restore && $conn->getLBInfo( 'master' ) ) {
-                                       $this->undoTransactionRoundFlags( $conn );
-                               }
-                       }
-               );
-
-               if ( $failures ) {
-                       throw new DBExpectedError(
-                               null,
-                               "Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
-                       );
-               }
+               $this->commitMasterChanges( $fname );
+               $this->flushMasterSnapshots( $fname );
+               $this->flushReplicaSnapshots( $fname );
        }
 
        public function finalizeMasterChanges() {
+               $this->assertTransactionRoundStage( [ self::ROUND_CURSORY, self::ROUND_FINALIZED ] );
+
+               $this->trxRoundStage = self::ROUND_ERROR; // "failed" until proven otherwise
+               // Loop until callbacks stop adding callbacks on other connections
+               $total = 0;
+               do {
+                       $count = 0; // callbacks execution attempts
+                       $this->forEachOpenMasterConnection( function ( Database $conn ) use ( &$count ) {
+                               // Run any pre-commit callbacks while leaving the post-commit ones suppressed.
+                               // Any error should cause all (peer) transactions to be rolled back together.
+                               $count += $conn->runOnTransactionPreCommitCallbacks();
+                       } );
+                       $total += $count;
+               } while ( $count > 0 );
+               // Defer post-commit callbacks until after COMMIT/ROLLBACK happens on all handles
                $this->forEachOpenMasterConnection( function ( Database $conn ) {
-                       // Any error should cause all DB transactions to be rolled back together
-                       $conn->setTrxEndCallbackSuppression( false );
-                       $conn->runOnTransactionPreCommitCallbacks();
-                       // Defer post-commit callbacks until COMMIT finishes for all DBs
                        $conn->setTrxEndCallbackSuppression( true );
                } );
+               $this->trxRoundStage = self::ROUND_FINALIZED;
+
+               return $total;
        }
 
        public function approveMasterChanges( array $options ) {
+               $this->assertTransactionRoundStage( self::ROUND_FINALIZED );
+
                $limit = isset( $options['maxWriteDuration'] ) ? $options['maxWriteDuration'] : 0;
+
+               $this->trxRoundStage = self::ROUND_ERROR; // "failed" until proven otherwise
                $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( $limit ) {
                        // If atomic sections or explicit transactions are still open, some caller must have
                        // caught an exception but failed to properly rollback any changes. Detect that and
@@ -1310,6 +1321,7 @@ class LoadBalancer implements ILoadBalancer {
                                );
                        }
                } );
+               $this->trxRoundStage = self::ROUND_APPROVED;
        }
 
        public function beginMasterChanges( $fname = __METHOD__ ) {
@@ -1319,32 +1331,26 @@ class LoadBalancer implements ILoadBalancer {
                                "$fname: Transaction round '{$this->trxRoundId}' already started."
                        );
                }
-               $this->trxRoundId = $fname;
+               $this->assertTransactionRoundStage( self::ROUND_CURSORY );
 
-               $failures = [];
-               $this->forEachOpenMasterConnection(
-                       function ( Database $conn ) use ( $fname, &$failures ) {
-                               $conn->setTrxEndCallbackSuppression( true );
-                               try {
-                                       $conn->flushSnapshot( $fname );
-                               } catch ( DBError $e ) {
-                                       call_user_func( $this->errorLogger, $e );
-                                       $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
-                               }
-                               $conn->setTrxEndCallbackSuppression( false );
-                               $this->applyTransactionRoundFlags( $conn );
-                       }
-               );
+               // Clear any empty transactions (no writes/callbacks) from the implicit round
+               $this->flushMasterSnapshots( $fname );
 
-               if ( $failures ) {
-                       throw new DBExpectedError(
-                               null,
-                               "$fname: Flush failed on server(s) " . implode( "\n", array_unique( $failures ) )
-                       );
-               }
+               $this->trxRoundId = $fname;
+               $this->trxRoundStage = self::ROUND_ERROR; // "failed" until proven otherwise
+               // Mark applicable handles as participating in this explicit transaction round.
+               // For each of these handles, any writes and callbacks will be tied to a single
+               // transaction. The (peer) handles will reject begin()/commit() calls unless they
+               // are part of an en masse commit or an en masse rollback.
+               $this->forEachOpenMasterConnection( function ( Database $conn ) {
+                       $this->applyTransactionRoundFlags( $conn );
+               } );
+               $this->trxRoundStage = self::ROUND_CURSORY;
        }
 
        public function commitMasterChanges( $fname = __METHOD__ ) {
+               $this->assertTransactionRoundStage( self::ROUND_APPROVED );
+
                $failures = [];
 
                /** @noinspection PhpUnusedLocalVariableInspection */
@@ -1352,62 +1358,125 @@ class LoadBalancer implements ILoadBalancer {
 
                $restore = ( $this->trxRoundId !== false );
                $this->trxRoundId = false;
+               $this->trxRoundStage = self::ROUND_ERROR; // "failed" until proven otherwise
+               // Commit any writes and clear any snapshots as well (callbacks require AUTOCOMMIT).
+               // Note that callbacks should already be suppressed due to finalizeMasterChanges().
                $this->forEachOpenMasterConnection(
-                       function ( IDatabase $conn ) use ( $fname, $restore, &$failures ) {
+                       function ( IDatabase $conn ) use ( $fname, &$failures ) {
                                try {
-                                       if ( $conn->writesOrCallbacksPending() ) {
-                                               $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
-                                       } elseif ( $restore ) {
-                                               $conn->flushSnapshot( $fname );
-                                       }
+                                       $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
                                } catch ( DBError $e ) {
                                        call_user_func( $this->errorLogger, $e );
                                        $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
                                }
-                               if ( $restore ) {
-                                       $this->undoTransactionRoundFlags( $conn );
-                               }
                        }
                );
-
                if ( $failures ) {
-                       throw new DBExpectedError(
+                       throw new DBTransactionError(
                                null,
                                "$fname: Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
                        );
                }
+               if ( $restore ) {
+                       // Unmark handles as participating in this explicit transaction round
+                       $this->forEachOpenMasterConnection( function ( Database $conn ) {
+                               $this->undoTransactionRoundFlags( $conn );
+                       } );
+               }
+               $this->trxRoundStage = self::ROUND_COMMIT_CALLBACKS;
        }
 
-       public function runMasterPostTrxCallbacks( $type ) {
+       public function runMasterTransactionIdleCallbacks() {
+               if ( $this->trxRoundStage === self::ROUND_COMMIT_CALLBACKS ) {
+                       $type = IDatabase::TRIGGER_COMMIT;
+               } elseif ( $this->trxRoundStage === self::ROUND_ROLLBACK_CALLBACKS ) {
+                       $type = IDatabase::TRIGGER_ROLLBACK;
+               } else {
+                       throw new DBTransactionError(
+                               null,
+                               "Transaction should be in the callback stage (not '{$this->trxRoundStage}')"
+                       );
+               }
+
+               $oldStage = $this->trxRoundStage;
+               $this->trxRoundStage = self::ROUND_ERROR; // "failed" until proven otherwise
+
+               // Now that the COMMIT/ROLLBACK step is over, enable post-commit callback runs
+               $this->forEachOpenMasterConnection( function ( Database $conn ) {
+                       $conn->setTrxEndCallbackSuppression( false );
+               } );
+
                $e = null; // first exception
+               // Loop until callbacks stop adding callbacks on other connections
+               do {
+                       // Run any pending callbacks for each connection...
+                       $count = 0; // callback execution attempts
+                       $this->forEachOpenMasterConnection(
+                               function ( Database $conn ) use ( $type, &$e, &$count ) {
+                                       if ( $conn->trxLevel() ) {
+                                               return; // retry in the next iteration, after commit() is called
+                                       }
+                                       try {
+                                               $count += $conn->runOnTransactionIdleCallbacks( $type );
+                                       } catch ( Exception $ex ) {
+                                               $e = $e ?: $ex;
+                                       }
+                               }
+                       );
+                       // Clear out any active transactions left over from callbacks...
+                       $this->forEachOpenMasterConnection( function ( Database $conn ) use ( &$e ) {
+                               if ( $conn->writesPending() ) {
+                                       // A callback from another handle wrote to this one and DBO_TRX is set
+                                       $this->queryLogger->warning( __METHOD__ . ": found writes pending." );
+                                       $fnames = implode( ', ', $conn->pendingWriteAndCallbackCallers() );
+                                       $this->queryLogger->warning(
+                                               __METHOD__ . ": found writes pending ($fnames).",
+                                               [
+                                                       'db_server' => $conn->getServer(),
+                                                       'db_name' => $conn->getDBname()
+                                               ]
+                                       );
+                               } elseif ( $conn->trxLevel() ) {
+                                       // A callback from another handle read from this one and DBO_TRX is set,
+                                       // which can easily happen if there is only one DB (no replicas)
+                                       $this->queryLogger->debug( __METHOD__ . ": found empty transaction." );
+                               }
+                               try {
+                                       $conn->commit( __METHOD__, $conn::FLUSHING_ALL_PEERS );
+                               } catch ( Exception $ex ) {
+                                       $e = $e ?: $ex;
+                               }
+                       } );
+               } while ( $count > 0 );
+
+               $this->trxRoundStage = $oldStage;
+
+               return $e;
+       }
+
+       public function runMasterTransactionListenerCallbacks() {
+               if ( $this->trxRoundStage === self::ROUND_COMMIT_CALLBACKS ) {
+                       $type = IDatabase::TRIGGER_COMMIT;
+               } elseif ( $this->trxRoundStage === self::ROUND_ROLLBACK_CALLBACKS ) {
+                       $type = IDatabase::TRIGGER_ROLLBACK;
+               } else {
+                       throw new DBTransactionError(
+                               null,
+                               "Transaction should be in the callback stage (not '{$this->trxRoundStage}')"
+                       );
+               }
+
+               $e = null;
+
+               $this->trxRoundStage = self::ROUND_ERROR; // "failed" until proven otherwise
                $this->forEachOpenMasterConnection( function ( Database $conn ) use ( $type, &$e ) {
-                       $conn->setTrxEndCallbackSuppression( false );
-                       // Callbacks run in AUTO-COMMIT mode, so make sure no transactions are pending...
-                       if ( $conn->writesPending() ) {
-                               // This happens if onTransactionIdle() callbacks write to *other* handles
-                               // (which already finished their callbacks). Let any callbacks run in the final
-                               // commitMasterChanges() in LBFactory::shutdown(), when the transaction is gone.
-                               $this->queryLogger->warning( __METHOD__ . ": found writes pending." );
-                               return;
-                       } elseif ( $conn->trxLevel() ) {
-                               // This happens for single-DB setups where DB_REPLICA uses the master DB,
-                               // thus leaving an implicit read-only transaction open at this point. It
-                               // also happens if onTransactionIdle() callbacks leave implicit transactions
-                               // open on *other* DBs (which is slightly improper). Let these COMMIT on the
-                               // next call to commitMasterChanges(), possibly in LBFactory::shutdown().
-                               return;
-                       }
-                       try {
-                               $conn->runOnTransactionIdleCallbacks( $type );
-                       } catch ( Exception $ex ) {
-                               $e = $e ?: $ex;
-                       }
                        try {
                                $conn->runTransactionListenerCallbacks( $type );
                        } catch ( Exception $ex ) {
                                $e = $e ?: $ex;
                        }
                } );
+               $this->trxRoundStage = self::ROUND_CURSORY;
 
                return $e;
        }
@@ -1415,20 +1484,37 @@ class LoadBalancer implements ILoadBalancer {
        public function rollbackMasterChanges( $fname = __METHOD__ ) {
                $restore = ( $this->trxRoundId !== false );
                $this->trxRoundId = false;
-               $this->forEachOpenMasterConnection(
-                       function ( IDatabase $conn ) use ( $fname, $restore ) {
-                               $conn->rollback( $fname, $conn::FLUSHING_ALL_PEERS );
-                               if ( $restore ) {
-                                       $this->undoTransactionRoundFlags( $conn );
-                               }
-                       }
-               );
+               $this->trxRoundStage = self::ROUND_ERROR; // "failed" until proven otherwise
+               $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( $fname ) {
+                       $conn->rollback( $fname, $conn::FLUSHING_ALL_PEERS );
+               } );
+               if ( $restore ) {
+                       // Unmark handles as participating in this explicit transaction round
+                       $this->forEachOpenMasterConnection( function ( Database $conn ) {
+                               $this->undoTransactionRoundFlags( $conn );
+                       } );
+               }
+               $this->trxRoundStage = self::ROUND_ROLLBACK_CALLBACKS;
        }
 
-       public function suppressTransactionEndCallbacks() {
-               $this->forEachOpenMasterConnection( function ( Database $conn ) {
-                       $conn->setTrxEndCallbackSuppression( true );
-               } );
+       /**
+        * @param string|string[] $stage
+        */
+       private function assertTransactionRoundStage( $stage ) {
+               $stages = (array)$stage;
+
+               if ( !in_array( $this->trxRoundStage, $stages, true ) ) {
+                       $stageList = implode(
+                               '/',
+                               array_map( function ( $v ) {
+                                       return "'$v'";
+                               }, $stages )
+                       );
+                       throw new DBTransactionError(
+                               null,
+                               "Transaction round stage must be $stageList (not '{$this->trxRoundStage}')"
+                       );
+               }
        }
 
        /**
@@ -1438,9 +1524,9 @@ class LoadBalancer implements ILoadBalancer {
         * transaction rounds and remain in auto-commit mode. Such behavior might be desired
         * when a DB server is used for something like simple key/value storage.
         *
-        * @param IDatabase $conn
+        * @param Database $conn
         */
-       private function applyTransactionRoundFlags( IDatabase $conn ) {
+       private function applyTransactionRoundFlags( Database $conn ) {
                if ( $conn->getLBInfo( 'autoCommitOnly' ) ) {
                        return; // transaction rounds do not apply to these connections
                }
@@ -1457,9 +1543,9 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        /**
-        * @param IDatabase $conn
+        * @param Database $conn
         */
-       private function undoTransactionRoundFlags( IDatabase $conn ) {
+       private function undoTransactionRoundFlags( Database $conn ) {
                if ( $conn->getLBInfo( 'autoCommitOnly' ) ) {
                        return; // transaction rounds do not apply to these connections
                }
@@ -1474,11 +1560,25 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        public function flushReplicaSnapshots( $fname = __METHOD__ ) {
-               $this->forEachOpenReplicaConnection( function ( IDatabase $conn ) {
-                       $conn->flushSnapshot( __METHOD__ );
+               $this->forEachOpenReplicaConnection( function ( IDatabase $conn ) use ( $fname ) {
+                       $conn->flushSnapshot( $fname );
                } );
        }
 
+       private function flushMasterSnapshots( $fname = __METHOD__ ) {
+               $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( $fname ) {
+                       $conn->flushSnapshot( $fname );
+               } );
+       }
+
+       /**
+        * @return string
+        * @since 1.32
+        */
+       public function getTransactionRoundStage() {
+               return $this->trxRoundStage;
+       }
+
        public function hasMasterConnection() {
                return $this->isOpen( $this->getWriterIndex() );
        }
index e17ac03..31c196a 100644 (file)
@@ -156,7 +156,8 @@ abstract class LogEntryBase implements LogEntry {
 }
 
 /**
- * This class wraps around database result row.
+ * A value class to process existing log entries. In other words, this class caches a log
+ * entry from the database and provides an immutable object-oriented representation of it.
  *
  * @since 1.19
  */
@@ -361,6 +362,10 @@ class DatabaseLogEntry extends LogEntryBase {
        }
 }
 
+/**
+ * A subclass of DatabaseLogEntry for objects constructed from entries in the
+ * recentchanges table (rather than the logging table).
+ */
 class RCDatabaseLogEntry extends DatabaseLogEntry {
 
        public function getId() {
@@ -425,7 +430,7 @@ class RCDatabaseLogEntry extends DatabaseLogEntry {
 }
 
 /**
- * Class for creating log entries manually, to inject them into the database.
+ * Class for creating new log entries and inserting them into the database.
  *
  * @since 1.19
  */
@@ -776,7 +781,7 @@ class ManualLogEntry extends LogEntryBase {
                                                        $tags = [];
                                                }
                                                $rc->addTags( $tags );
-                                               $rc->save( 'pleasedontudp' );
+                                               $rc->save( $rc::SEND_NONE );
                                        }
 
                                        if ( $to === 'udp' || $to === 'rcandudp' ) {
index bc0491f..0ffe691 100644 (file)
@@ -108,6 +108,12 @@ class LogFormatter {
         */
        private $linkRenderer;
 
+       /**
+        * @see LogFormatter::getMessageParameters
+        * @var array
+        */
+       protected $parsedParameters;
+
        protected function __construct( LogEntry $entry ) {
                $this->entry = $entry;
                $this->context = RequestContext::getMain();
index 24fdfb0..c047e96 100644 (file)
@@ -65,10 +65,11 @@ class LogPager extends ReverseChronologicalPager {
         * @param int|bool $month The month to start from. Default: false
         * @param string $tagFilter Tag
         * @param string $action Specific action (subtype) requested
+        * @param int $logId Log entry ID, to limit to a single log entry.
         */
        public function __construct( $list, $types = [], $performer = '', $title = '',
                $pattern = '', $conds = [], $year = false, $month = false, $tagFilter = '',
-               $action = ''
+               $action = '', $logId = false
        ) {
                parent::__construct( $list->getContext() );
                $this->mConds = $conds;
@@ -81,6 +82,7 @@ class LogPager extends ReverseChronologicalPager {
                $this->limitAction( $action );
                $this->getDateCond( $year, $month );
                $this->mTagFilter = $tagFilter;
+               $this->limitLogId( $logId );
 
                $this->mDb = wfGetDB( DB_REPLICA, 'logpager' );
        }
@@ -278,6 +280,17 @@ class LogPager extends ReverseChronologicalPager {
                }
        }
 
+       /**
+        * Limit to the (single) specified log ID.
+        * @param int $logId The log entry ID.
+        */
+       protected function limitLogId( $logId ) {
+               if ( !$logId ) {
+                       return;
+               }
+               $this->mConds['log_id'] = $logId;
+       }
+
        /**
         * Constructs the most part of the query. Extra conditions are sprinkled in
         * all over this class.
index fb0f2f9..7b48ad0 100644 (file)
@@ -189,6 +189,46 @@ class UserMailer {
                return self::sendInternal( $to, $from, $subject, $body, $options );
        }
 
+       /**
+        * Whether the PEAR Mail_mime library is usable. This will
+        * try and load it if it is not already.
+        *
+        * @return bool
+        */
+       private static function isMailMimeUsable() {
+               static $usable = null;
+               if ( $usable === null ) {
+                       // If the class is not already loaded, and it's in the include path,
+                       // try requiring it.
+                       if ( !class_exists( 'Mail_mime' ) && stream_resolve_include_path( 'Mail/mime.php' ) ) {
+                               require_once 'Mail/mime.php';
+                       }
+                       $usable = class_exists( 'Mail_mime' );
+               }
+
+               return $usable;
+       }
+
+       /**
+        * Whether the PEAR Mail library is usable. This will
+        * try and load it if it is not already.
+        *
+        * @return bool
+        */
+       private static function isMailUsable() {
+               static $usable = null;
+               if ( $usable === null ) {
+                       // If the class is not already loaded, and it's in the include path,
+                       // try requiring it.
+                       if ( !class_exists( 'Mail' ) && stream_resolve_include_path( 'Mail.php' ) ) {
+                               require_once 'Mail.php';
+                       }
+                       $usable = class_exists( 'Mail' );
+               }
+
+               return $usable;
+       }
+
        /**
         * Helper function fo UserMailer::send() which does the actual sending. It expects a $to
         * list which the UserMailerSplitTo hook would not split further.
@@ -296,15 +336,12 @@ class UserMailer {
                if ( is_array( $body ) ) {
                        // we are sending a multipart message
                        wfDebug( "Assembling multipart mime email\n" );
-                       if ( !stream_resolve_include_path( 'Mail/mime.php' ) ) {
+                       if ( !self::isMailMimeUsable() ) {
                                wfDebug( "PEAR Mail_Mime package is not installed. Falling back to text email.\n" );
                                // remove the html body for text email fall back
                                $body = $body['text'];
                        } else {
-                               // Check if pear/mail_mime is already loaded (via composer)
-                               if ( !class_exists( 'Mail_mime' ) ) {
-                                       require_once 'Mail/mime.php';
-                               }
+                               // pear/mail_mime is already loaded by this point
                                if ( wfIsWindows() ) {
                                        $body['text'] = str_replace( "\n", "\r\n", $body['text'] );
                                        $body['html'] = str_replace( "\n", "\r\n", $body['html'] );
@@ -352,12 +389,8 @@ class UserMailer {
 
                if ( is_array( $wgSMTP ) ) {
                        // Check if pear/mail is already loaded (via composer)
-                       if ( !class_exists( 'Mail' ) ) {
-                               // PEAR MAILER
-                               if ( !stream_resolve_include_path( 'Mail.php' ) ) {
-                                       throw new MWException( 'PEAR mail package is not installed' );
-                               }
-                               require_once 'Mail.php';
+                       if ( !self::isMailUsable() ) {
+                               throw new MWException( 'PEAR mail package is not installed' );
                        }
 
                        Wikimedia\suppressWarnings();
diff --git a/includes/media/BMP.php b/includes/media/BMP.php
deleted file mode 100644 (file)
index 0229ac1..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-/**
- * Handler for Microsoft's bitmap format.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for Microsoft's bitmap format; getimagesize() doesn't
- * support these files
- *
- * @ingroup Media
- */
-class BmpHandler extends BitmapHandler {
-       /**
-        * @param File $file
-        * @return bool
-        */
-       public function mustRender( $file ) {
-               return true;
-       }
-
-       /**
-        * Render files as PNG
-        *
-        * @param string $text
-        * @param string $mime
-        * @param array $params
-        * @return array
-        */
-       function getThumbType( $text, $mime, $params = null ) {
-               return [ 'png', 'image/png' ];
-       }
-
-       /**
-        * Get width and height from the bmp header.
-        *
-        * @param File|FSFile $image
-        * @param string $filename
-        * @return array
-        */
-       function getImageSize( $image, $filename ) {
-               $f = fopen( $filename, 'rb' );
-               if ( !$f ) {
-                       return false;
-               }
-               $header = fread( $f, 54 );
-               fclose( $f );
-
-               // Extract binary form of width and height from the header
-               $w = substr( $header, 18, 4 );
-               $h = substr( $header, 22, 4 );
-
-               // Convert the unsigned long 32 bits (little endian):
-               try {
-                       $w = wfUnpack( 'V', $w, 4 );
-                       $h = wfUnpack( 'V', $h, 4 );
-               } catch ( Exception $e ) {
-                       return false;
-               }
-
-               return [ $w[1], $h[1] ];
-       }
-}
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
deleted file mode 100644 (file)
index cda037c..0000000
+++ /dev/null
@@ -1,607 +0,0 @@
-<?php
-/**
- * Generic handler for bitmap images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Generic handler for bitmap images
- *
- * @ingroup Media
- */
-class BitmapHandler extends TransformationalImageHandler {
-
-       /**
-        * Returns which scaler type should be used. Creates parent directories
-        * for $dstPath and returns 'client' on error
-        *
-        * @param string $dstPath
-        * @param bool $checkDstPath
-        * @return string|Callable One of client, im, custom, gd, imext or an array( object, method )
-        */
-       protected function getScalerType( $dstPath, $checkDstPath = true ) {
-               global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand;
-
-               if ( !$dstPath && $checkDstPath ) {
-                       # No output path available, client side scaling only
-                       $scaler = 'client';
-               } elseif ( !$wgUseImageResize ) {
-                       $scaler = 'client';
-               } elseif ( $wgUseImageMagick ) {
-                       $scaler = 'im';
-               } elseif ( $wgCustomConvertCommand ) {
-                       $scaler = 'custom';
-               } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
-                       $scaler = 'gd';
-               } elseif ( class_exists( 'Imagick' ) ) {
-                       $scaler = 'imext';
-               } else {
-                       $scaler = 'client';
-               }
-
-               return $scaler;
-       }
-
-       public function makeParamString( $params ) {
-               $res = parent::makeParamString( $params );
-               if ( isset( $params['interlace'] ) && $params['interlace'] ) {
-                       return "interlaced-{$res}";
-               } else {
-                       return $res;
-               }
-       }
-
-       public function parseParamString( $str ) {
-               $remainder = preg_replace( '/^interlaced-/', '', $str );
-               $params = parent::parseParamString( $remainder );
-               if ( $params === false ) {
-                       return false;
-               }
-               $params['interlace'] = $str !== $remainder;
-               return $params;
-       }
-
-       public function validateParam( $name, $value ) {
-               if ( $name === 'interlace' ) {
-                       return $value === false || $value === true;
-               } else {
-                       return parent::validateParam( $name, $value );
-               }
-       }
-
-       /**
-        * @param File $image
-        * @param array &$params
-        * @return bool
-        */
-       function normaliseParams( $image, &$params ) {
-               global $wgMaxInterlacingAreas;
-               if ( !parent::normaliseParams( $image, $params ) ) {
-                       return false;
-               }
-               $mimeType = $image->getMimeType();
-               $interlace = isset( $params['interlace'] ) && $params['interlace']
-                       && isset( $wgMaxInterlacingAreas[$mimeType] )
-                       && $this->getImageArea( $image ) <= $wgMaxInterlacingAreas[$mimeType];
-               $params['interlace'] = $interlace;
-               return true;
-       }
-
-       /**
-        * Get ImageMagick subsampling factors for the target JPEG pixel format.
-        *
-        * @param string $pixelFormat one of 'yuv444', 'yuv422', 'yuv420'
-        * @return array of string keys
-        */
-       protected function imageMagickSubsampling( $pixelFormat ) {
-               switch ( $pixelFormat ) {
-                       case 'yuv444':
-                               return [ '1x1', '1x1', '1x1' ];
-                       case 'yuv422':
-                               return [ '2x1', '1x1', '1x1' ];
-                       case 'yuv420':
-                               return [ '2x2', '1x1', '1x1' ];
-                       default:
-                               throw new MWException( 'Invalid pixel format for JPEG output' );
-               }
-       }
-
-       /**
-        * Transform an image using ImageMagick
-        *
-        * @param File $image File associated with this thumbnail
-        * @param array $params Array with scaler params
-        *
-        * @return MediaTransformError|bool Error object if error occurred, false (=no error) otherwise
-        */
-       protected function transformImageMagick( $image, $params ) {
-               # use ImageMagick
-               global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea,
-                       $wgImageMagickTempDir, $wgImageMagickConvertCommand, $wgJpegPixelFormat,
-                       $wgJpegQuality;
-
-               $quality = [];
-               $sharpen = [];
-               $scene = false;
-               $animation_pre = [];
-               $animation_post = [];
-               $decoderHint = [];
-               $subsampling = [];
-
-               if ( $params['mimeType'] == 'image/jpeg' ) {
-                       $qualityVal = isset( $params['quality'] ) ? (string)$params['quality'] : null;
-                       $quality = [ '-quality', $qualityVal ?: (string)$wgJpegQuality ]; // 80% by default
-                       if ( $params['interlace'] ) {
-                               $animation_post = [ '-interlace', 'JPEG' ];
-                       }
-                       # Sharpening, see T8193
-                       if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
-                               / ( $params['srcWidth'] + $params['srcHeight'] )
-                               < $wgSharpenReductionThreshold
-                       ) {
-                               $sharpen = [ '-sharpen', $wgSharpenParameter ];
-                       }
-                       if ( version_compare( $this->getMagickVersion(), "6.5.6" ) >= 0 ) {
-                               // JPEG decoder hint to reduce memory, available since IM 6.5.6-2
-                               $decoderHint = [ '-define', "jpeg:size={$params['physicalDimensions']}" ];
-                       }
-                       if ( $wgJpegPixelFormat ) {
-                               $factors = $this->imageMagickSubsampling( $wgJpegPixelFormat );
-                               $subsampling = [ '-sampling-factor', implode( ',', $factors ) ];
-                       }
-               } elseif ( $params['mimeType'] == 'image/png' ) {
-                       $quality = [ '-quality', '95' ]; // zlib 9, adaptive filtering
-                       if ( $params['interlace'] ) {
-                               $animation_post = [ '-interlace', 'PNG' ];
-                       }
-               } elseif ( $params['mimeType'] == 'image/webp' ) {
-                       $quality = [ '-quality', '95' ]; // zlib 9, adaptive filtering
-               } elseif ( $params['mimeType'] == 'image/gif' ) {
-                       if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
-                               // Extract initial frame only; we're so big it'll
-                               // be a total drag. :P
-                               $scene = 0;
-                       } elseif ( $this->isAnimatedImage( $image ) ) {
-                               // Coalesce is needed to scale animated GIFs properly (T3017).
-                               $animation_pre = [ '-coalesce' ];
-                               // We optimize the output, but -optimize is broken,
-                               // use optimizeTransparency instead (T13822)
-                               if ( version_compare( $this->getMagickVersion(), "6.3.5" ) >= 0 ) {
-                                       $animation_post = [ '-fuzz', '5%', '-layers', 'optimizeTransparency' ];
-                               }
-                       }
-                       if ( $params['interlace'] && version_compare( $this->getMagickVersion(), "6.3.4" ) >= 0
-                               && !$this->isAnimatedImage( $image ) ) { // interlacing animated GIFs is a bad idea
-                               $animation_post[] = '-interlace';
-                               $animation_post[] = 'GIF';
-                       }
-               } elseif ( $params['mimeType'] == 'image/x-xcf' ) {
-                       // Before merging layers, we need to set the background
-                       // to be transparent to preserve alpha, as -layers merge
-                       // merges all layers on to a canvas filled with the
-                       // background colour. After merging we reset the background
-                       // to be white for the default background colour setting
-                       // in the PNG image (which is used in old IE)
-                       $animation_pre = [
-                               '-background', 'transparent',
-                               '-layers', 'merge',
-                               '-background', 'white',
-                       ];
-                       Wikimedia\suppressWarnings();
-                       $xcfMeta = unserialize( $image->getMetadata() );
-                       Wikimedia\restoreWarnings();
-                       if ( $xcfMeta
-                               && isset( $xcfMeta['colorType'] )
-                               && $xcfMeta['colorType'] === 'greyscale-alpha'
-                               && version_compare( $this->getMagickVersion(), "6.8.9-3" ) < 0
-                       ) {
-                               // T68323 - Greyscale images not rendered properly.
-                               // So only take the "red" channel.
-                               $channelOnly = [ '-channel', 'R', '-separate' ];
-                               $animation_pre = array_merge( $animation_pre, $channelOnly );
-                       }
-               }
-
-               // Use one thread only, to avoid deadlock bugs on OOM
-               $env = [ 'OMP_NUM_THREADS' => 1 ];
-               if ( strval( $wgImageMagickTempDir ) !== '' ) {
-                       $env['MAGICK_TMPDIR'] = $wgImageMagickTempDir;
-               }
-
-               $rotation = isset( $params['disableRotation'] ) ? 0 : $this->getRotation( $image );
-               list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
-
-               $cmd = call_user_func_array( 'wfEscapeShellArg', array_merge(
-                       [ $wgImageMagickConvertCommand ],
-                       $quality,
-                       // Specify white background color, will be used for transparent images
-                       // in Internet Explorer/Windows instead of default black.
-                       [ '-background', 'white' ],
-                       $decoderHint,
-                       [ $this->escapeMagickInput( $params['srcPath'], $scene ) ],
-                       $animation_pre,
-                       // For the -thumbnail option a "!" is needed to force exact size,
-                       // or ImageMagick may decide your ratio is wrong and slice off
-                       // a pixel.
-                       [ '-thumbnail', "{$width}x{$height}!" ],
-                       // Add the source url as a comment to the thumb, but don't add the flag if there's no comment
-                       ( $params['comment'] !== ''
-                               ? [ '-set', 'comment', $this->escapeMagickProperty( $params['comment'] ) ]
-                               : [] ),
-                       // T108616: Avoid exposure of local file path
-                       [ '+set', 'Thumb::URI' ],
-                       [ '-depth', 8 ],
-                       $sharpen,
-                       [ '-rotate', "-$rotation" ],
-                       $subsampling,
-                       $animation_post,
-                       [ $this->escapeMagickOutput( $params['dstPath'] ) ] ) );
-
-               wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
-               $retval = 0;
-               $err = wfShellExecWithStderr( $cmd, $retval, $env );
-
-               if ( $retval !== 0 ) {
-                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
-
-                       return $this->getMediaTransformError( $params, "$err\nError code: $retval" );
-               }
-
-               return false; # No error
-       }
-
-       /**
-        * Transform an image using the Imagick PHP extension
-        *
-        * @param File $image File associated with this thumbnail
-        * @param array $params Array with scaler params
-        *
-        * @return MediaTransformError Error|bool object if error occurred, false (=no error) otherwise
-        */
-       protected function transformImageMagickExt( $image, $params ) {
-               global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea,
-                       $wgJpegPixelFormat, $wgJpegQuality;
-
-               try {
-                       $im = new Imagick();
-                       $im->readImage( $params['srcPath'] );
-
-                       if ( $params['mimeType'] == 'image/jpeg' ) {
-                               // Sharpening, see T8193
-                               if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
-                                       / ( $params['srcWidth'] + $params['srcHeight'] )
-                                       < $wgSharpenReductionThreshold
-                               ) {
-                                       // Hack, since $wgSharpenParameter is written specifically for the command line convert
-                                       list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter );
-                                       $im->sharpenImage( $radius, $sigma );
-                               }
-                               $qualityVal = isset( $params['quality'] ) ? (string)$params['quality'] : null;
-                               $im->setCompressionQuality( $qualityVal ?: $wgJpegQuality );
-                               if ( $params['interlace'] ) {
-                                       $im->setInterlaceScheme( Imagick::INTERLACE_JPEG );
-                               }
-                               if ( $wgJpegPixelFormat ) {
-                                       $factors = $this->imageMagickSubsampling( $wgJpegPixelFormat );
-                                       $im->setSamplingFactors( $factors );
-                               }
-                       } elseif ( $params['mimeType'] == 'image/png' ) {
-                               $im->setCompressionQuality( 95 );
-                               if ( $params['interlace'] ) {
-                                       $im->setInterlaceScheme( Imagick::INTERLACE_PNG );
-                               }
-                       } elseif ( $params['mimeType'] == 'image/gif' ) {
-                               if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
-                                       // Extract initial frame only; we're so big it'll
-                                       // be a total drag. :P
-                                       $im->setImageScene( 0 );
-                               } elseif ( $this->isAnimatedImage( $image ) ) {
-                                       // Coalesce is needed to scale animated GIFs properly (T3017).
-                                       $im = $im->coalesceImages();
-                               }
-                               // GIF interlacing is only available since 6.3.4
-                               $v = Imagick::getVersion();
-                               preg_match( '/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $v );
-
-                               if ( $params['interlace'] && version_compare( $v[1], '6.3.4' ) >= 0 ) {
-                                       $im->setInterlaceScheme( Imagick::INTERLACE_GIF );
-                               }
-                       }
-
-                       $rotation = isset( $params['disableRotation'] ) ? 0 : $this->getRotation( $image );
-                       list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
-
-                       $im->setImageBackgroundColor( new ImagickPixel( 'white' ) );
-
-                       // Call Imagick::thumbnailImage on each frame
-                       foreach ( $im as $i => $frame ) {
-                               if ( !$frame->thumbnailImage( $width, $height, /* fit */ false ) ) {
-                                       return $this->getMediaTransformError( $params, "Error scaling frame $i" );
-                               }
-                       }
-                       $im->setImageDepth( 8 );
-
-                       if ( $rotation ) {
-                               if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) {
-                                       return $this->getMediaTransformError( $params, "Error rotating $rotation degrees" );
-                               }
-                       }
-
-                       if ( $this->isAnimatedImage( $image ) ) {
-                               wfDebug( __METHOD__ . ": Writing animated thumbnail\n" );
-                               // This is broken somehow... can't find out how to fix it
-                               $result = $im->writeImages( $params['dstPath'], true );
-                       } else {
-                               $result = $im->writeImage( $params['dstPath'] );
-                       }
-                       if ( !$result ) {
-                               return $this->getMediaTransformError( $params,
-                                       "Unable to write thumbnail to {$params['dstPath']}" );
-                       }
-               } catch ( ImagickException $e ) {
-                       return $this->getMediaTransformError( $params, $e->getMessage() );
-               }
-
-               return false;
-       }
-
-       /**
-        * Transform an image using a custom command
-        *
-        * @param File $image File associated with this thumbnail
-        * @param array $params Array with scaler params
-        *
-        * @return MediaTransformError Error|bool object if error occurred, false (=no error) otherwise
-        */
-       protected function transformCustom( $image, $params ) {
-               # Use a custom convert command
-               global $wgCustomConvertCommand;
-
-               # Variables: %s %d %w %h
-               $src = wfEscapeShellArg( $params['srcPath'] );
-               $dst = wfEscapeShellArg( $params['dstPath'] );
-               $cmd = $wgCustomConvertCommand;
-               $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
-               $cmd = str_replace( '%h', wfEscapeShellArg( $params['physicalHeight'] ),
-                       str_replace( '%w', wfEscapeShellArg( $params['physicalWidth'] ), $cmd ) ); # Size
-               wfDebug( __METHOD__ . ": Running custom convert command $cmd\n" );
-               $retval = 0;
-               $err = wfShellExecWithStderr( $cmd, $retval );
-
-               if ( $retval !== 0 ) {
-                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
-
-                       return $this->getMediaTransformError( $params, $err );
-               }
-
-               return false; # No error
-       }
-
-       /**
-        * Transform an image using the built in GD library
-        *
-        * @param File $image File associated with this thumbnail
-        * @param array $params Array with scaler params
-        *
-        * @return MediaTransformError|bool Error object if error occurred, false (=no error) otherwise
-        */
-       protected function transformGd( $image, $params ) {
-               # Use PHP's builtin GD library functions.
-               # First find out what kind of file this is, and select the correct
-               # input routine for this.
-
-               $typemap = [
-                       'image/gif' => [ 'imagecreatefromgif', 'palette', false, 'imagegif' ],
-                       'image/jpeg' => [ 'imagecreatefromjpeg', 'truecolor', true,
-                               [ __CLASS__, 'imageJpegWrapper' ] ],
-                       'image/png' => [ 'imagecreatefrompng', 'bits', false, 'imagepng' ],
-                       'image/vnd.wap.wbmp' => [ 'imagecreatefromwbmp', 'palette', false, 'imagewbmp' ],
-                       'image/xbm' => [ 'imagecreatefromxbm', 'palette', false, 'imagexbm' ],
-               ];
-
-               if ( !isset( $typemap[$params['mimeType']] ) ) {
-                       $err = 'Image type not supported';
-                       wfDebug( "$err\n" );
-                       $errMsg = wfMessage( 'thumbnail_image-type' )->text();
-
-                       return $this->getMediaTransformError( $params, $errMsg );
-               }
-               list( $loader, $colorStyle, $useQuality, $saveType ) = $typemap[$params['mimeType']];
-
-               if ( !function_exists( $loader ) ) {
-                       $err = "Incomplete GD library configuration: missing function $loader";
-                       wfDebug( "$err\n" );
-                       $errMsg = wfMessage( 'thumbnail_gd-library', $loader )->text();
-
-                       return $this->getMediaTransformError( $params, $errMsg );
-               }
-
-               if ( !file_exists( $params['srcPath'] ) ) {
-                       $err = "File seems to be missing: {$params['srcPath']}";
-                       wfDebug( "$err\n" );
-                       $errMsg = wfMessage( 'thumbnail_image-missing', $params['srcPath'] )->text();
-
-                       return $this->getMediaTransformError( $params, $errMsg );
-               }
-
-               if ( filesize( $params['srcPath'] ) === 0 ) {
-                       $err = "Image file size seems to be zero.";
-                       wfDebug( "$err\n" );
-                       $errMsg = wfMessage( 'thumbnail_image-size-zero', $params['srcPath'] )->text();
-
-                       return $this->getMediaTransformError( $params, $errMsg );
-               }
-
-               $src_image = call_user_func( $loader, $params['srcPath'] );
-
-               $rotation = function_exists( 'imagerotate' ) && !isset( $params['disableRotation'] ) ?
-                       $this->getRotation( $image ) :
-                       0;
-               list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
-               $dst_image = imagecreatetruecolor( $width, $height );
-
-               // Initialise the destination image to transparent instead of
-               // the default solid black, to support PNG and GIF transparency nicely
-               $background = imagecolorallocate( $dst_image, 0, 0, 0 );
-               imagecolortransparent( $dst_image, $background );
-               imagealphablending( $dst_image, false );
-
-               if ( $colorStyle == 'palette' ) {
-                       // Don't resample for paletted GIF images.
-                       // It may just uglify them, and completely breaks transparency.
-                       imagecopyresized( $dst_image, $src_image,
-                               0, 0, 0, 0,
-                               $width, $height,
-                               imagesx( $src_image ), imagesy( $src_image ) );
-               } else {
-                       imagecopyresampled( $dst_image, $src_image,
-                               0, 0, 0, 0,
-                               $width, $height,
-                               imagesx( $src_image ), imagesy( $src_image ) );
-               }
-
-               if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) {
-                       $rot_image = imagerotate( $dst_image, $rotation, 0 );
-                       imagedestroy( $dst_image );
-                       $dst_image = $rot_image;
-               }
-
-               imagesavealpha( $dst_image, true );
-
-               $funcParams = [ $dst_image, $params['dstPath'] ];
-               if ( $useQuality && isset( $params['quality'] ) ) {
-                       $funcParams[] = $params['quality'];
-               }
-               call_user_func_array( $saveType, $funcParams );
-
-               imagedestroy( $dst_image );
-               imagedestroy( $src_image );
-
-               return false; # No error
-       }
-
-       /**
-        * Callback for transformGd when transforming jpeg images.
-        *
-        * @param resource $dst_image Image resource of the original image
-        * @param string $thumbPath File path to write the thumbnail image to
-        * @param int|null $quality Quality of the thumbnail from 1-100,
-        *    or null to use default quality.
-        */
-       static function imageJpegWrapper( $dst_image, $thumbPath, $quality = null ) {
-               global $wgJpegQuality;
-
-               if ( $quality === null ) {
-                       $quality = $wgJpegQuality;
-               }
-
-               imageinterlace( $dst_image );
-               imagejpeg( $dst_image, $thumbPath, $quality );
-       }
-
-       /**
-        * Returns whether the current scaler supports rotation (im and gd do)
-        *
-        * @return bool
-        */
-       public function canRotate() {
-               $scaler = $this->getScalerType( null, false );
-               switch ( $scaler ) {
-                       case 'im':
-                               # ImageMagick supports autorotation
-                               return true;
-                       case 'imext':
-                               # Imagick::rotateImage
-                               return true;
-                       case 'gd':
-                               # GD's imagerotate function is used to rotate images, but not
-                               # all precompiled PHP versions have that function
-                               return function_exists( 'imagerotate' );
-                       default:
-                               # Other scalers don't support rotation
-                               return false;
-               }
-       }
-
-       /**
-        * @see $wgEnableAutoRotation
-        * @return bool Whether auto rotation is enabled
-        */
-       public function autoRotateEnabled() {
-               global $wgEnableAutoRotation;
-
-               if ( $wgEnableAutoRotation === null ) {
-                       // Only enable auto-rotation when we actually can
-                       return $this->canRotate();
-               }
-
-               return $wgEnableAutoRotation;
-       }
-
-       /**
-        * @param File $file
-        * @param array $params Rotate parameters.
-        *   'rotation' clockwise rotation in degrees, allowed are multiples of 90
-        * @since 1.21
-        * @return bool|MediaTransformError
-        */
-       public function rotate( $file, $params ) {
-               global $wgImageMagickConvertCommand;
-
-               $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
-               $scene = false;
-
-               $scaler = $this->getScalerType( null, false );
-               switch ( $scaler ) {
-                       case 'im':
-                               $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " .
-                                       wfEscapeShellArg( $this->escapeMagickInput( $params['srcPath'], $scene ) ) .
-                                       " -rotate " . wfEscapeShellArg( "-$rotation" ) . " " .
-                                       wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) );
-                               wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
-                               $retval = 0;
-                               $err = wfShellExecWithStderr( $cmd, $retval );
-                               if ( $retval !== 0 ) {
-                                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
-
-                                       return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
-                               }
-
-                               return false;
-                       case 'imext':
-                               $im = new Imagick();
-                               $im->readImage( $params['srcPath'] );
-                               if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) {
-                                       return new MediaTransformError( 'thumbnail_error', 0, 0,
-                                               "Error rotating $rotation degrees" );
-                               }
-                               $result = $im->writeImage( $params['dstPath'] );
-                               if ( !$result ) {
-                                       return new MediaTransformError( 'thumbnail_error', 0, 0,
-                                               "Unable to write image to {$params['dstPath']}" );
-                               }
-
-                               return false;
-                       default:
-                               return new MediaTransformError( 'thumbnail_error', 0, 0,
-                                       "$scaler rotation not implemented" );
-               }
-       }
-}
diff --git a/includes/media/BitmapHandler.php b/includes/media/BitmapHandler.php
new file mode 100644 (file)
index 0000000..cda037c
--- /dev/null
@@ -0,0 +1,607 @@
+<?php
+/**
+ * Generic handler for bitmap images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Generic handler for bitmap images
+ *
+ * @ingroup Media
+ */
+class BitmapHandler extends TransformationalImageHandler {
+
+       /**
+        * Returns which scaler type should be used. Creates parent directories
+        * for $dstPath and returns 'client' on error
+        *
+        * @param string $dstPath
+        * @param bool $checkDstPath
+        * @return string|Callable One of client, im, custom, gd, imext or an array( object, method )
+        */
+       protected function getScalerType( $dstPath, $checkDstPath = true ) {
+               global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand;
+
+               if ( !$dstPath && $checkDstPath ) {
+                       # No output path available, client side scaling only
+                       $scaler = 'client';
+               } elseif ( !$wgUseImageResize ) {
+                       $scaler = 'client';
+               } elseif ( $wgUseImageMagick ) {
+                       $scaler = 'im';
+               } elseif ( $wgCustomConvertCommand ) {
+                       $scaler = 'custom';
+               } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
+                       $scaler = 'gd';
+               } elseif ( class_exists( 'Imagick' ) ) {
+                       $scaler = 'imext';
+               } else {
+                       $scaler = 'client';
+               }
+
+               return $scaler;
+       }
+
+       public function makeParamString( $params ) {
+               $res = parent::makeParamString( $params );
+               if ( isset( $params['interlace'] ) && $params['interlace'] ) {
+                       return "interlaced-{$res}";
+               } else {
+                       return $res;
+               }
+       }
+
+       public function parseParamString( $str ) {
+               $remainder = preg_replace( '/^interlaced-/', '', $str );
+               $params = parent::parseParamString( $remainder );
+               if ( $params === false ) {
+                       return false;
+               }
+               $params['interlace'] = $str !== $remainder;
+               return $params;
+       }
+
+       public function validateParam( $name, $value ) {
+               if ( $name === 'interlace' ) {
+                       return $value === false || $value === true;
+               } else {
+                       return parent::validateParam( $name, $value );
+               }
+       }
+
+       /**
+        * @param File $image
+        * @param array &$params
+        * @return bool
+        */
+       function normaliseParams( $image, &$params ) {
+               global $wgMaxInterlacingAreas;
+               if ( !parent::normaliseParams( $image, $params ) ) {
+                       return false;
+               }
+               $mimeType = $image->getMimeType();
+               $interlace = isset( $params['interlace'] ) && $params['interlace']
+                       && isset( $wgMaxInterlacingAreas[$mimeType] )
+                       && $this->getImageArea( $image ) <= $wgMaxInterlacingAreas[$mimeType];
+               $params['interlace'] = $interlace;
+               return true;
+       }
+
+       /**
+        * Get ImageMagick subsampling factors for the target JPEG pixel format.
+        *
+        * @param string $pixelFormat one of 'yuv444', 'yuv422', 'yuv420'
+        * @return array of string keys
+        */
+       protected function imageMagickSubsampling( $pixelFormat ) {
+               switch ( $pixelFormat ) {
+                       case 'yuv444':
+                               return [ '1x1', '1x1', '1x1' ];
+                       case 'yuv422':
+                               return [ '2x1', '1x1', '1x1' ];
+                       case 'yuv420':
+                               return [ '2x2', '1x1', '1x1' ];
+                       default:
+                               throw new MWException( 'Invalid pixel format for JPEG output' );
+               }
+       }
+
+       /**
+        * Transform an image using ImageMagick
+        *
+        * @param File $image File associated with this thumbnail
+        * @param array $params Array with scaler params
+        *
+        * @return MediaTransformError|bool Error object if error occurred, false (=no error) otherwise
+        */
+       protected function transformImageMagick( $image, $params ) {
+               # use ImageMagick
+               global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea,
+                       $wgImageMagickTempDir, $wgImageMagickConvertCommand, $wgJpegPixelFormat,
+                       $wgJpegQuality;
+
+               $quality = [];
+               $sharpen = [];
+               $scene = false;
+               $animation_pre = [];
+               $animation_post = [];
+               $decoderHint = [];
+               $subsampling = [];
+
+               if ( $params['mimeType'] == 'image/jpeg' ) {
+                       $qualityVal = isset( $params['quality'] ) ? (string)$params['quality'] : null;
+                       $quality = [ '-quality', $qualityVal ?: (string)$wgJpegQuality ]; // 80% by default
+                       if ( $params['interlace'] ) {
+                               $animation_post = [ '-interlace', 'JPEG' ];
+                       }
+                       # Sharpening, see T8193
+                       if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
+                               / ( $params['srcWidth'] + $params['srcHeight'] )
+                               < $wgSharpenReductionThreshold
+                       ) {
+                               $sharpen = [ '-sharpen', $wgSharpenParameter ];
+                       }
+                       if ( version_compare( $this->getMagickVersion(), "6.5.6" ) >= 0 ) {
+                               // JPEG decoder hint to reduce memory, available since IM 6.5.6-2
+                               $decoderHint = [ '-define', "jpeg:size={$params['physicalDimensions']}" ];
+                       }
+                       if ( $wgJpegPixelFormat ) {
+                               $factors = $this->imageMagickSubsampling( $wgJpegPixelFormat );
+                               $subsampling = [ '-sampling-factor', implode( ',', $factors ) ];
+                       }
+               } elseif ( $params['mimeType'] == 'image/png' ) {
+                       $quality = [ '-quality', '95' ]; // zlib 9, adaptive filtering
+                       if ( $params['interlace'] ) {
+                               $animation_post = [ '-interlace', 'PNG' ];
+                       }
+               } elseif ( $params['mimeType'] == 'image/webp' ) {
+                       $quality = [ '-quality', '95' ]; // zlib 9, adaptive filtering
+               } elseif ( $params['mimeType'] == 'image/gif' ) {
+                       if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
+                               // Extract initial frame only; we're so big it'll
+                               // be a total drag. :P
+                               $scene = 0;
+                       } elseif ( $this->isAnimatedImage( $image ) ) {
+                               // Coalesce is needed to scale animated GIFs properly (T3017).
+                               $animation_pre = [ '-coalesce' ];
+                               // We optimize the output, but -optimize is broken,
+                               // use optimizeTransparency instead (T13822)
+                               if ( version_compare( $this->getMagickVersion(), "6.3.5" ) >= 0 ) {
+                                       $animation_post = [ '-fuzz', '5%', '-layers', 'optimizeTransparency' ];
+                               }
+                       }
+                       if ( $params['interlace'] && version_compare( $this->getMagickVersion(), "6.3.4" ) >= 0
+                               && !$this->isAnimatedImage( $image ) ) { // interlacing animated GIFs is a bad idea
+                               $animation_post[] = '-interlace';
+                               $animation_post[] = 'GIF';
+                       }
+               } elseif ( $params['mimeType'] == 'image/x-xcf' ) {
+                       // Before merging layers, we need to set the background
+                       // to be transparent to preserve alpha, as -layers merge
+                       // merges all layers on to a canvas filled with the
+                       // background colour. After merging we reset the background
+                       // to be white for the default background colour setting
+                       // in the PNG image (which is used in old IE)
+                       $animation_pre = [
+                               '-background', 'transparent',
+                               '-layers', 'merge',
+                               '-background', 'white',
+                       ];
+                       Wikimedia\suppressWarnings();
+                       $xcfMeta = unserialize( $image->getMetadata() );
+                       Wikimedia\restoreWarnings();
+                       if ( $xcfMeta
+                               && isset( $xcfMeta['colorType'] )
+                               && $xcfMeta['colorType'] === 'greyscale-alpha'
+                               && version_compare( $this->getMagickVersion(), "6.8.9-3" ) < 0
+                       ) {
+                               // T68323 - Greyscale images not rendered properly.
+                               // So only take the "red" channel.
+                               $channelOnly = [ '-channel', 'R', '-separate' ];
+                               $animation_pre = array_merge( $animation_pre, $channelOnly );
+                       }
+               }
+
+               // Use one thread only, to avoid deadlock bugs on OOM
+               $env = [ 'OMP_NUM_THREADS' => 1 ];
+               if ( strval( $wgImageMagickTempDir ) !== '' ) {
+                       $env['MAGICK_TMPDIR'] = $wgImageMagickTempDir;
+               }
+
+               $rotation = isset( $params['disableRotation'] ) ? 0 : $this->getRotation( $image );
+               list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
+
+               $cmd = call_user_func_array( 'wfEscapeShellArg', array_merge(
+                       [ $wgImageMagickConvertCommand ],
+                       $quality,
+                       // Specify white background color, will be used for transparent images
+                       // in Internet Explorer/Windows instead of default black.
+                       [ '-background', 'white' ],
+                       $decoderHint,
+                       [ $this->escapeMagickInput( $params['srcPath'], $scene ) ],
+                       $animation_pre,
+                       // For the -thumbnail option a "!" is needed to force exact size,
+                       // or ImageMagick may decide your ratio is wrong and slice off
+                       // a pixel.
+                       [ '-thumbnail', "{$width}x{$height}!" ],
+                       // Add the source url as a comment to the thumb, but don't add the flag if there's no comment
+                       ( $params['comment'] !== ''
+                               ? [ '-set', 'comment', $this->escapeMagickProperty( $params['comment'] ) ]
+                               : [] ),
+                       // T108616: Avoid exposure of local file path
+                       [ '+set', 'Thumb::URI' ],
+                       [ '-depth', 8 ],
+                       $sharpen,
+                       [ '-rotate', "-$rotation" ],
+                       $subsampling,
+                       $animation_post,
+                       [ $this->escapeMagickOutput( $params['dstPath'] ) ] ) );
+
+               wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
+               $retval = 0;
+               $err = wfShellExecWithStderr( $cmd, $retval, $env );
+
+               if ( $retval !== 0 ) {
+                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
+
+                       return $this->getMediaTransformError( $params, "$err\nError code: $retval" );
+               }
+
+               return false; # No error
+       }
+
+       /**
+        * Transform an image using the Imagick PHP extension
+        *
+        * @param File $image File associated with this thumbnail
+        * @param array $params Array with scaler params
+        *
+        * @return MediaTransformError Error|bool object if error occurred, false (=no error) otherwise
+        */
+       protected function transformImageMagickExt( $image, $params ) {
+               global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea,
+                       $wgJpegPixelFormat, $wgJpegQuality;
+
+               try {
+                       $im = new Imagick();
+                       $im->readImage( $params['srcPath'] );
+
+                       if ( $params['mimeType'] == 'image/jpeg' ) {
+                               // Sharpening, see T8193
+                               if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
+                                       / ( $params['srcWidth'] + $params['srcHeight'] )
+                                       < $wgSharpenReductionThreshold
+                               ) {
+                                       // Hack, since $wgSharpenParameter is written specifically for the command line convert
+                                       list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter );
+                                       $im->sharpenImage( $radius, $sigma );
+                               }
+                               $qualityVal = isset( $params['quality'] ) ? (string)$params['quality'] : null;
+                               $im->setCompressionQuality( $qualityVal ?: $wgJpegQuality );
+                               if ( $params['interlace'] ) {
+                                       $im->setInterlaceScheme( Imagick::INTERLACE_JPEG );
+                               }
+                               if ( $wgJpegPixelFormat ) {
+                                       $factors = $this->imageMagickSubsampling( $wgJpegPixelFormat );
+                                       $im->setSamplingFactors( $factors );
+                               }
+                       } elseif ( $params['mimeType'] == 'image/png' ) {
+                               $im->setCompressionQuality( 95 );
+                               if ( $params['interlace'] ) {
+                                       $im->setInterlaceScheme( Imagick::INTERLACE_PNG );
+                               }
+                       } elseif ( $params['mimeType'] == 'image/gif' ) {
+                               if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
+                                       // Extract initial frame only; we're so big it'll
+                                       // be a total drag. :P
+                                       $im->setImageScene( 0 );
+                               } elseif ( $this->isAnimatedImage( $image ) ) {
+                                       // Coalesce is needed to scale animated GIFs properly (T3017).
+                                       $im = $im->coalesceImages();
+                               }
+                               // GIF interlacing is only available since 6.3.4
+                               $v = Imagick::getVersion();
+                               preg_match( '/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $v );
+
+                               if ( $params['interlace'] && version_compare( $v[1], '6.3.4' ) >= 0 ) {
+                                       $im->setInterlaceScheme( Imagick::INTERLACE_GIF );
+                               }
+                       }
+
+                       $rotation = isset( $params['disableRotation'] ) ? 0 : $this->getRotation( $image );
+                       list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
+
+                       $im->setImageBackgroundColor( new ImagickPixel( 'white' ) );
+
+                       // Call Imagick::thumbnailImage on each frame
+                       foreach ( $im as $i => $frame ) {
+                               if ( !$frame->thumbnailImage( $width, $height, /* fit */ false ) ) {
+                                       return $this->getMediaTransformError( $params, "Error scaling frame $i" );
+                               }
+                       }
+                       $im->setImageDepth( 8 );
+
+                       if ( $rotation ) {
+                               if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) {
+                                       return $this->getMediaTransformError( $params, "Error rotating $rotation degrees" );
+                               }
+                       }
+
+                       if ( $this->isAnimatedImage( $image ) ) {
+                               wfDebug( __METHOD__ . ": Writing animated thumbnail\n" );
+                               // This is broken somehow... can't find out how to fix it
+                               $result = $im->writeImages( $params['dstPath'], true );
+                       } else {
+                               $result = $im->writeImage( $params['dstPath'] );
+                       }
+                       if ( !$result ) {
+                               return $this->getMediaTransformError( $params,
+                                       "Unable to write thumbnail to {$params['dstPath']}" );
+                       }
+               } catch ( ImagickException $e ) {
+                       return $this->getMediaTransformError( $params, $e->getMessage() );
+               }
+
+               return false;
+       }
+
+       /**
+        * Transform an image using a custom command
+        *
+        * @param File $image File associated with this thumbnail
+        * @param array $params Array with scaler params
+        *
+        * @return MediaTransformError Error|bool object if error occurred, false (=no error) otherwise
+        */
+       protected function transformCustom( $image, $params ) {
+               # Use a custom convert command
+               global $wgCustomConvertCommand;
+
+               # Variables: %s %d %w %h
+               $src = wfEscapeShellArg( $params['srcPath'] );
+               $dst = wfEscapeShellArg( $params['dstPath'] );
+               $cmd = $wgCustomConvertCommand;
+               $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
+               $cmd = str_replace( '%h', wfEscapeShellArg( $params['physicalHeight'] ),
+                       str_replace( '%w', wfEscapeShellArg( $params['physicalWidth'] ), $cmd ) ); # Size
+               wfDebug( __METHOD__ . ": Running custom convert command $cmd\n" );
+               $retval = 0;
+               $err = wfShellExecWithStderr( $cmd, $retval );
+
+               if ( $retval !== 0 ) {
+                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
+
+                       return $this->getMediaTransformError( $params, $err );
+               }
+
+               return false; # No error
+       }
+
+       /**
+        * Transform an image using the built in GD library
+        *
+        * @param File $image File associated with this thumbnail
+        * @param array $params Array with scaler params
+        *
+        * @return MediaTransformError|bool Error object if error occurred, false (=no error) otherwise
+        */
+       protected function transformGd( $image, $params ) {
+               # Use PHP's builtin GD library functions.
+               # First find out what kind of file this is, and select the correct
+               # input routine for this.
+
+               $typemap = [
+                       'image/gif' => [ 'imagecreatefromgif', 'palette', false, 'imagegif' ],
+                       'image/jpeg' => [ 'imagecreatefromjpeg', 'truecolor', true,
+                               [ __CLASS__, 'imageJpegWrapper' ] ],
+                       'image/png' => [ 'imagecreatefrompng', 'bits', false, 'imagepng' ],
+                       'image/vnd.wap.wbmp' => [ 'imagecreatefromwbmp', 'palette', false, 'imagewbmp' ],
+                       'image/xbm' => [ 'imagecreatefromxbm', 'palette', false, 'imagexbm' ],
+               ];
+
+               if ( !isset( $typemap[$params['mimeType']] ) ) {
+                       $err = 'Image type not supported';
+                       wfDebug( "$err\n" );
+                       $errMsg = wfMessage( 'thumbnail_image-type' )->text();
+
+                       return $this->getMediaTransformError( $params, $errMsg );
+               }
+               list( $loader, $colorStyle, $useQuality, $saveType ) = $typemap[$params['mimeType']];
+
+               if ( !function_exists( $loader ) ) {
+                       $err = "Incomplete GD library configuration: missing function $loader";
+                       wfDebug( "$err\n" );
+                       $errMsg = wfMessage( 'thumbnail_gd-library', $loader )->text();
+
+                       return $this->getMediaTransformError( $params, $errMsg );
+               }
+
+               if ( !file_exists( $params['srcPath'] ) ) {
+                       $err = "File seems to be missing: {$params['srcPath']}";
+                       wfDebug( "$err\n" );
+                       $errMsg = wfMessage( 'thumbnail_image-missing', $params['srcPath'] )->text();
+
+                       return $this->getMediaTransformError( $params, $errMsg );
+               }
+
+               if ( filesize( $params['srcPath'] ) === 0 ) {
+                       $err = "Image file size seems to be zero.";
+                       wfDebug( "$err\n" );
+                       $errMsg = wfMessage( 'thumbnail_image-size-zero', $params['srcPath'] )->text();
+
+                       return $this->getMediaTransformError( $params, $errMsg );
+               }
+
+               $src_image = call_user_func( $loader, $params['srcPath'] );
+
+               $rotation = function_exists( 'imagerotate' ) && !isset( $params['disableRotation'] ) ?
+                       $this->getRotation( $image ) :
+                       0;
+               list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
+               $dst_image = imagecreatetruecolor( $width, $height );
+
+               // Initialise the destination image to transparent instead of
+               // the default solid black, to support PNG and GIF transparency nicely
+               $background = imagecolorallocate( $dst_image, 0, 0, 0 );
+               imagecolortransparent( $dst_image, $background );
+               imagealphablending( $dst_image, false );
+
+               if ( $colorStyle == 'palette' ) {
+                       // Don't resample for paletted GIF images.
+                       // It may just uglify them, and completely breaks transparency.
+                       imagecopyresized( $dst_image, $src_image,
+                               0, 0, 0, 0,
+                               $width, $height,
+                               imagesx( $src_image ), imagesy( $src_image ) );
+               } else {
+                       imagecopyresampled( $dst_image, $src_image,
+                               0, 0, 0, 0,
+                               $width, $height,
+                               imagesx( $src_image ), imagesy( $src_image ) );
+               }
+
+               if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) {
+                       $rot_image = imagerotate( $dst_image, $rotation, 0 );
+                       imagedestroy( $dst_image );
+                       $dst_image = $rot_image;
+               }
+
+               imagesavealpha( $dst_image, true );
+
+               $funcParams = [ $dst_image, $params['dstPath'] ];
+               if ( $useQuality && isset( $params['quality'] ) ) {
+                       $funcParams[] = $params['quality'];
+               }
+               call_user_func_array( $saveType, $funcParams );
+
+               imagedestroy( $dst_image );
+               imagedestroy( $src_image );
+
+               return false; # No error
+       }
+
+       /**
+        * Callback for transformGd when transforming jpeg images.
+        *
+        * @param resource $dst_image Image resource of the original image
+        * @param string $thumbPath File path to write the thumbnail image to
+        * @param int|null $quality Quality of the thumbnail from 1-100,
+        *    or null to use default quality.
+        */
+       static function imageJpegWrapper( $dst_image, $thumbPath, $quality = null ) {
+               global $wgJpegQuality;
+
+               if ( $quality === null ) {
+                       $quality = $wgJpegQuality;
+               }
+
+               imageinterlace( $dst_image );
+               imagejpeg( $dst_image, $thumbPath, $quality );
+       }
+
+       /**
+        * Returns whether the current scaler supports rotation (im and gd do)
+        *
+        * @return bool
+        */
+       public function canRotate() {
+               $scaler = $this->getScalerType( null, false );
+               switch ( $scaler ) {
+                       case 'im':
+                               # ImageMagick supports autorotation
+                               return true;
+                       case 'imext':
+                               # Imagick::rotateImage
+                               return true;
+                       case 'gd':
+                               # GD's imagerotate function is used to rotate images, but not
+                               # all precompiled PHP versions have that function
+                               return function_exists( 'imagerotate' );
+                       default:
+                               # Other scalers don't support rotation
+                               return false;
+               }
+       }
+
+       /**
+        * @see $wgEnableAutoRotation
+        * @return bool Whether auto rotation is enabled
+        */
+       public function autoRotateEnabled() {
+               global $wgEnableAutoRotation;
+
+               if ( $wgEnableAutoRotation === null ) {
+                       // Only enable auto-rotation when we actually can
+                       return $this->canRotate();
+               }
+
+               return $wgEnableAutoRotation;
+       }
+
+       /**
+        * @param File $file
+        * @param array $params Rotate parameters.
+        *   'rotation' clockwise rotation in degrees, allowed are multiples of 90
+        * @since 1.21
+        * @return bool|MediaTransformError
+        */
+       public function rotate( $file, $params ) {
+               global $wgImageMagickConvertCommand;
+
+               $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
+               $scene = false;
+
+               $scaler = $this->getScalerType( null, false );
+               switch ( $scaler ) {
+                       case 'im':
+                               $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " .
+                                       wfEscapeShellArg( $this->escapeMagickInput( $params['srcPath'], $scene ) ) .
+                                       " -rotate " . wfEscapeShellArg( "-$rotation" ) . " " .
+                                       wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) );
+                               wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
+                               $retval = 0;
+                               $err = wfShellExecWithStderr( $cmd, $retval );
+                               if ( $retval !== 0 ) {
+                                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
+
+                                       return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
+                               }
+
+                               return false;
+                       case 'imext':
+                               $im = new Imagick();
+                               $im->readImage( $params['srcPath'] );
+                               if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) {
+                                       return new MediaTransformError( 'thumbnail_error', 0, 0,
+                                               "Error rotating $rotation degrees" );
+                               }
+                               $result = $im->writeImage( $params['dstPath'] );
+                               if ( !$result ) {
+                                       return new MediaTransformError( 'thumbnail_error', 0, 0,
+                                               "Unable to write image to {$params['dstPath']}" );
+                               }
+
+                               return false;
+                       default:
+                               return new MediaTransformError( 'thumbnail_error', 0, 0,
+                                       "$scaler rotation not implemented" );
+               }
+       }
+}
diff --git a/includes/media/BitmapHandler_ClientOnly.php b/includes/media/BitmapHandler_ClientOnly.php
new file mode 100644 (file)
index 0000000..fa5b0a6
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Handler for bitmap images that will be resized by clients.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for bitmap images that will be resized by clients.
+ *
+ * This is not used by default but can be assigned to some image types
+ * using $wgMediaHandlers.
+ *
+ * @ingroup Media
+ */
+// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+class BitmapHandler_ClientOnly extends BitmapHandler {
+
+       /**
+        * @param File $image
+        * @param array &$params
+        * @return bool
+        */
+       function normaliseParams( $image, &$params ) {
+               return ImageHandler::normaliseParams( $image, $params );
+       }
+
+       /**
+        * @param File $image
+        * @param string $dstPath
+        * @param string $dstUrl
+        * @param array $params
+        * @param int $flags
+        * @return ThumbnailImage|TransformParameterError
+        */
+       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+               if ( !$this->normaliseParams( $image, $params ) ) {
+                       return new TransformParameterError( $params );
+               }
+
+               return new ThumbnailImage( $image, $image->getUrl(), $image->getLocalRefPath(), $params );
+       }
+}
diff --git a/includes/media/Bitmap_ClientOnly.php b/includes/media/Bitmap_ClientOnly.php
deleted file mode 100644 (file)
index fa5b0a6..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-/**
- * Handler for bitmap images that will be resized by clients.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for bitmap images that will be resized by clients.
- *
- * This is not used by default but can be assigned to some image types
- * using $wgMediaHandlers.
- *
- * @ingroup Media
- */
-// phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
-class BitmapHandler_ClientOnly extends BitmapHandler {
-
-       /**
-        * @param File $image
-        * @param array &$params
-        * @return bool
-        */
-       function normaliseParams( $image, &$params ) {
-               return ImageHandler::normaliseParams( $image, $params );
-       }
-
-       /**
-        * @param File $image
-        * @param string $dstPath
-        * @param string $dstUrl
-        * @param array $params
-        * @param int $flags
-        * @return ThumbnailImage|TransformParameterError
-        */
-       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
-               if ( !$this->normaliseParams( $image, $params ) ) {
-                       return new TransformParameterError( $params );
-               }
-
-               return new ThumbnailImage( $image, $image->getUrl(), $image->getLocalRefPath(), $params );
-       }
-}
diff --git a/includes/media/BmpHandler.php b/includes/media/BmpHandler.php
new file mode 100644 (file)
index 0000000..0229ac1
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Handler for Microsoft's bitmap format.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for Microsoft's bitmap format; getimagesize() doesn't
+ * support these files
+ *
+ * @ingroup Media
+ */
+class BmpHandler extends BitmapHandler {
+       /**
+        * @param File $file
+        * @return bool
+        */
+       public function mustRender( $file ) {
+               return true;
+       }
+
+       /**
+        * Render files as PNG
+        *
+        * @param string $text
+        * @param string $mime
+        * @param array $params
+        * @return array
+        */
+       function getThumbType( $text, $mime, $params = null ) {
+               return [ 'png', 'image/png' ];
+       }
+
+       /**
+        * Get width and height from the bmp header.
+        *
+        * @param File|FSFile $image
+        * @param string $filename
+        * @return array
+        */
+       function getImageSize( $image, $filename ) {
+               $f = fopen( $filename, 'rb' );
+               if ( !$f ) {
+                       return false;
+               }
+               $header = fread( $f, 54 );
+               fclose( $f );
+
+               // Extract binary form of width and height from the header
+               $w = substr( $header, 18, 4 );
+               $h = substr( $header, 22, 4 );
+
+               // Convert the unsigned long 32 bits (little endian):
+               try {
+                       $w = wfUnpack( 'V', $w, 4 );
+                       $h = wfUnpack( 'V', $h, 4 );
+               } catch ( Exception $e ) {
+                       return false;
+               }
+
+               return [ $w[1], $h[1] ];
+       }
+}
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
deleted file mode 100644 (file)
index 2541e35..0000000
+++ /dev/null
@@ -1,464 +0,0 @@
-<?php
-/**
- * Handler for DjVu images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for DjVu images
- *
- * @ingroup Media
- */
-class DjVuHandler extends ImageHandler {
-       const EXPENSIVE_SIZE_LIMIT = 10485760; // 10MiB
-
-       /**
-        * @return bool
-        */
-       function isEnabled() {
-               global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML;
-               if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) {
-                       wfDebug( "DjVu is disabled, please set \$wgDjvuRenderer and \$wgDjvuDump\n" );
-
-                       return false;
-               } else {
-                       return true;
-               }
-       }
-
-       /**
-        * @param File $file
-        * @return bool
-        */
-       public function mustRender( $file ) {
-               return true;
-       }
-
-       /**
-        * True if creating thumbnails from the file is large or otherwise resource-intensive.
-        * @param File $file
-        * @return bool
-        */
-       public function isExpensiveToThumbnail( $file ) {
-               return $file->getSize() > static::EXPENSIVE_SIZE_LIMIT;
-       }
-
-       /**
-        * @param File $file
-        * @return bool
-        */
-       public function isMultiPage( $file ) {
-               return true;
-       }
-
-       /**
-        * @return array
-        */
-       public function getParamMap() {
-               return [
-                       'img_width' => 'width',
-                       'img_page' => 'page',
-               ];
-       }
-
-       /**
-        * @param string $name
-        * @param mixed $value
-        * @return bool
-        */
-       public function validateParam( $name, $value ) {
-               if ( $name === 'page' && trim( $value ) !== (string)intval( $value ) ) {
-                       // Extra junk on the end of page, probably actually a caption
-                       // e.g. [[File:Foo.djvu|thumb|Page 3 of the document shows foo]]
-                       return false;
-               }
-               if ( in_array( $name, [ 'width', 'height', 'page' ] ) ) {
-                       if ( $value <= 0 ) {
-                               return false;
-                       } else {
-                               return true;
-                       }
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * @param array $params
-        * @return bool|string
-        */
-       public function makeParamString( $params ) {
-               $page = isset( $params['page'] ) ? $params['page'] : 1;
-               if ( !isset( $params['width'] ) ) {
-                       return false;
-               }
-
-               return "page{$page}-{$params['width']}px";
-       }
-
-       /**
-        * @param string $str
-        * @return array|bool
-        */
-       public function parseParamString( $str ) {
-               $m = false;
-               if ( preg_match( '/^page(\d+)-(\d+)px$/', $str, $m ) ) {
-                       return [ 'width' => $m[2], 'page' => $m[1] ];
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * @param array $params
-        * @return array
-        */
-       function getScriptParams( $params ) {
-               return [
-                       'width' => $params['width'],
-                       'page' => $params['page'],
-               ];
-       }
-
-       /**
-        * @param File $image
-        * @param string $dstPath
-        * @param string $dstUrl
-        * @param array $params
-        * @param int $flags
-        * @return MediaTransformError|ThumbnailImage|TransformParameterError
-        */
-       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
-               global $wgDjvuRenderer, $wgDjvuPostProcessor;
-
-               if ( !$this->normaliseParams( $image, $params ) ) {
-                       return new TransformParameterError( $params );
-               }
-               $width = $params['width'];
-               $height = $params['height'];
-               $page = $params['page'];
-
-               if ( $flags & self::TRANSFORM_LATER ) {
-                       $params = [
-                               'width' => $width,
-                               'height' => $height,
-                               'page' => $page
-                       ];
-
-                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
-               }
-
-               if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
-                       return new MediaTransformError(
-                               'thumbnail_error',
-                               $width,
-                               $height,
-                               wfMessage( 'thumbnail_dest_directory' )
-                       );
-               }
-
-               // Get local copy source for shell scripts
-               // Thumbnail extraction is very inefficient for large files.
-               // Provide a way to pool count limit the number of downloaders.
-               if ( $image->getSize() >= 1e7 ) { // 10MB
-                       $work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $image->getName() ),
-                               [
-                                       'doWork' => function () use ( $image ) {
-                                               return $image->getLocalRefPath();
-                                       }
-                               ]
-                       );
-                       $srcPath = $work->execute();
-               } else {
-                       $srcPath = $image->getLocalRefPath();
-               }
-
-               if ( $srcPath === false ) { // Failed to get local copy
-                       wfDebugLog( 'thumbnail',
-                               sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
-                                       wfHostname(), $image->getName() ) );
-
-                       return new MediaTransformError( 'thumbnail_error',
-                               $params['width'], $params['height'],
-                               wfMessage( 'filemissing' )
-                       );
-               }
-
-               # Use a subshell (brackets) to aggregate stderr from both pipeline commands
-               # before redirecting it to the overall stdout. This works in both Linux and Windows XP.
-               $cmd = '(' . wfEscapeShellArg(
-                       $wgDjvuRenderer,
-                       "-format=ppm",
-                       "-page={$page}",
-                       "-size={$params['physicalWidth']}x{$params['physicalHeight']}",
-                       $srcPath );
-               if ( $wgDjvuPostProcessor ) {
-                       $cmd .= " | {$wgDjvuPostProcessor}";
-               }
-               $cmd .= ' > ' . wfEscapeShellArg( $dstPath ) . ') 2>&1';
-               wfDebug( __METHOD__ . ": $cmd\n" );
-               $retval = '';
-               $err = wfShellExec( $cmd, $retval );
-
-               $removed = $this->removeBadFile( $dstPath, $retval );
-               if ( $retval != 0 || $removed ) {
-                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
-                       return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
-               } else {
-                       $params = [
-                               'width' => $width,
-                               'height' => $height,
-                               'page' => $page
-                       ];
-
-                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
-               }
-       }
-
-       /**
-        * Cache an instance of DjVuImage in an Image object, return that instance
-        *
-        * @param File|FSFile $image
-        * @param string $path
-        * @return DjVuImage
-        */
-       function getDjVuImage( $image, $path ) {
-               if ( !$image ) {
-                       $deja = new DjVuImage( $path );
-               } elseif ( !isset( $image->dejaImage ) ) {
-                       $deja = $image->dejaImage = new DjVuImage( $path );
-               } else {
-                       $deja = $image->dejaImage;
-               }
-
-               return $deja;
-       }
-
-       /**
-        * Get metadata, unserializing it if neccessary.
-        *
-        * @param File $file The DjVu file in question
-        * @return string XML metadata as a string.
-        * @throws MWException
-        */
-       private function getUnserializedMetadata( File $file ) {
-               $metadata = $file->getMetadata();
-               if ( substr( $metadata, 0, 3 ) === '<?xml' ) {
-                       // Old style. Not serialized but instead just a raw string of XML.
-                       return $metadata;
-               }
-
-               Wikimedia\suppressWarnings();
-               $unser = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-               if ( is_array( $unser ) ) {
-                       if ( isset( $unser['error'] ) ) {
-                               return false;
-                       } elseif ( isset( $unser['xml'] ) ) {
-                               return $unser['xml'];
-                       } else {
-                               // Should never ever reach here.
-                               throw new MWException( "Error unserializing DjVu metadata." );
-                       }
-               }
-
-               // unserialize failed. Guess it wasn't really serialized after all,
-               return $metadata;
-       }
-
-       /**
-        * Cache a document tree for the DjVu XML metadata
-        * @param File $image
-        * @param bool $gettext DOCUMENT (Default: false)
-        * @return bool|SimpleXMLElement
-        */
-       public function getMetaTree( $image, $gettext = false ) {
-               if ( $gettext && isset( $image->djvuTextTree ) ) {
-                       return $image->djvuTextTree;
-               }
-               if ( !$gettext && isset( $image->dejaMetaTree ) ) {
-                       return $image->dejaMetaTree;
-               }
-
-               $metadata = $this->getUnserializedMetadata( $image );
-               if ( !$this->isMetadataValid( $image, $metadata ) ) {
-                       wfDebug( "DjVu XML metadata is invalid or missing, should have been fixed in upgradeRow\n" );
-
-                       return false;
-               }
-
-               $trees = $this->extractTreesFromMetadata( $metadata );
-               $image->djvuTextTree = $trees['TextTree'];
-               $image->dejaMetaTree = $trees['MetaTree'];
-
-               if ( $gettext ) {
-                       return $image->djvuTextTree;
-               } else {
-                       return $image->dejaMetaTree;
-               }
-       }
-
-       /**
-        * Extracts metadata and text trees from metadata XML in string form
-        * @param string $metadata XML metadata as a string
-        * @return array
-        */
-       protected function extractTreesFromMetadata( $metadata ) {
-               Wikimedia\suppressWarnings();
-               try {
-                       // Set to false rather than null to avoid further attempts
-                       $metaTree = false;
-                       $textTree = false;
-                       $tree = new SimpleXMLElement( $metadata, LIBXML_PARSEHUGE );
-                       if ( $tree->getName() == 'mw-djvu' ) {
-                               /** @var SimpleXMLElement $b */
-                               foreach ( $tree->children() as $b ) {
-                                       if ( $b->getName() == 'DjVuTxt' ) {
-                                               // @todo File::djvuTextTree and File::dejaMetaTree are declared
-                                               // dynamically. Add a public File::$data to facilitate this?
-                                               $textTree = $b;
-                                       } elseif ( $b->getName() == 'DjVuXML' ) {
-                                               $metaTree = $b;
-                                       }
-                               }
-                       } else {
-                               $metaTree = $tree;
-                       }
-               } catch ( Exception $e ) {
-                       wfDebug( "Bogus multipage XML metadata\n" );
-               }
-               Wikimedia\restoreWarnings();
-
-               return [ 'MetaTree' => $metaTree, 'TextTree' => $textTree ];
-       }
-
-       function getImageSize( $image, $path ) {
-               return $this->getDjVuImage( $image, $path )->getImageSize();
-       }
-
-       function getThumbType( $ext, $mime, $params = null ) {
-               global $wgDjvuOutputExtension;
-               static $mime;
-               if ( !isset( $mime ) ) {
-                       $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
-                       $mime = $magic->guessTypesForExtension( $wgDjvuOutputExtension );
-               }
-
-               return [ $wgDjvuOutputExtension, $mime ];
-       }
-
-       function getMetadata( $image, $path ) {
-               wfDebug( "Getting DjVu metadata for $path\n" );
-
-               $xml = $this->getDjVuImage( $image, $path )->retrieveMetaData();
-               if ( $xml === false ) {
-                       // Special value so that we don't repetitively try and decode a broken file.
-                       return serialize( [ 'error' => 'Error extracting metadata' ] );
-               } else {
-                       return serialize( [ 'xml' => $xml ] );
-               }
-       }
-
-       function getMetadataType( $image ) {
-               return 'djvuxml';
-       }
-
-       function isMetadataValid( $image, $metadata ) {
-               return !empty( $metadata ) && $metadata != serialize( [] );
-       }
-
-       function pageCount( File $image ) {
-               $info = $this->getDimensionInfo( $image );
-
-               return $info ? $info['pageCount'] : false;
-       }
-
-       function getPageDimensions( File $image, $page ) {
-               $index = $page - 1; // MW starts pages at 1
-
-               $info = $this->getDimensionInfo( $image );
-               if ( $info && isset( $info['dimensionsByPage'][$index] ) ) {
-                       return $info['dimensionsByPage'][$index];
-               }
-
-               return false;
-       }
-
-       protected function getDimensionInfo( File $file ) {
-               $cache = ObjectCache::getMainWANInstance();
-               return $cache->getWithSetCallback(
-                       $cache->makeKey( 'file-djvu', 'dimensions', $file->getSha1() ),
-                       $cache::TTL_INDEFINITE,
-                       function () use ( $file ) {
-                               $tree = $this->getMetaTree( $file );
-                               return $this->getDimensionInfoFromMetaTree( $tree );
-                       },
-                       [ 'pcTTL' => $cache::TTL_INDEFINITE ]
-               );
-       }
-
-       /**
-        * Given an XML metadata tree, returns dimension information about the document
-        * @param bool|SimpleXMLElement $metatree The file's XML metadata tree
-        * @return bool|array
-        */
-       protected function getDimensionInfoFromMetaTree( $metatree ) {
-               if ( !$metatree ) {
-                       return false;
-               }
-
-               $dimsByPage = [];
-               $count = count( $metatree->xpath( '//OBJECT' ) );
-               for ( $i = 0; $i < $count; $i++ ) {
-                       $o = $metatree->BODY[0]->OBJECT[$i];
-                       if ( $o ) {
-                               $dimsByPage[$i] = [
-                                       'width' => (int)$o['width'],
-                                       'height' => (int)$o['height'],
-                               ];
-                       } else {
-                               $dimsByPage[$i] = false;
-                       }
-               }
-
-               return [ 'pageCount' => $count, 'dimensionsByPage' => $dimsByPage ];
-       }
-
-       /**
-        * @param File $image
-        * @param int $page Page number to get information for
-        * @return bool|string Page text or false when no text found.
-        */
-       function getPageText( File $image, $page ) {
-               $tree = $this->getMetaTree( $image, true );
-               if ( !$tree ) {
-                       return false;
-               }
-
-               $o = $tree->BODY[0]->PAGE[$page - 1];
-               if ( $o ) {
-                       $txt = $o['value'];
-
-                       return $txt;
-               } else {
-                       return false;
-               }
-       }
-}
diff --git a/includes/media/DjVuHandler.php b/includes/media/DjVuHandler.php
new file mode 100644 (file)
index 0000000..2541e35
--- /dev/null
@@ -0,0 +1,464 @@
+<?php
+/**
+ * Handler for DjVu images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for DjVu images
+ *
+ * @ingroup Media
+ */
+class DjVuHandler extends ImageHandler {
+       const EXPENSIVE_SIZE_LIMIT = 10485760; // 10MiB
+
+       /**
+        * @return bool
+        */
+       function isEnabled() {
+               global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML;
+               if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) {
+                       wfDebug( "DjVu is disabled, please set \$wgDjvuRenderer and \$wgDjvuDump\n" );
+
+                       return false;
+               } else {
+                       return true;
+               }
+       }
+
+       /**
+        * @param File $file
+        * @return bool
+        */
+       public function mustRender( $file ) {
+               return true;
+       }
+
+       /**
+        * True if creating thumbnails from the file is large or otherwise resource-intensive.
+        * @param File $file
+        * @return bool
+        */
+       public function isExpensiveToThumbnail( $file ) {
+               return $file->getSize() > static::EXPENSIVE_SIZE_LIMIT;
+       }
+
+       /**
+        * @param File $file
+        * @return bool
+        */
+       public function isMultiPage( $file ) {
+               return true;
+       }
+
+       /**
+        * @return array
+        */
+       public function getParamMap() {
+               return [
+                       'img_width' => 'width',
+                       'img_page' => 'page',
+               ];
+       }
+
+       /**
+        * @param string $name
+        * @param mixed $value
+        * @return bool
+        */
+       public function validateParam( $name, $value ) {
+               if ( $name === 'page' && trim( $value ) !== (string)intval( $value ) ) {
+                       // Extra junk on the end of page, probably actually a caption
+                       // e.g. [[File:Foo.djvu|thumb|Page 3 of the document shows foo]]
+                       return false;
+               }
+               if ( in_array( $name, [ 'width', 'height', 'page' ] ) ) {
+                       if ( $value <= 0 ) {
+                               return false;
+                       } else {
+                               return true;
+                       }
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * @param array $params
+        * @return bool|string
+        */
+       public function makeParamString( $params ) {
+               $page = isset( $params['page'] ) ? $params['page'] : 1;
+               if ( !isset( $params['width'] ) ) {
+                       return false;
+               }
+
+               return "page{$page}-{$params['width']}px";
+       }
+
+       /**
+        * @param string $str
+        * @return array|bool
+        */
+       public function parseParamString( $str ) {
+               $m = false;
+               if ( preg_match( '/^page(\d+)-(\d+)px$/', $str, $m ) ) {
+                       return [ 'width' => $m[2], 'page' => $m[1] ];
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * @param array $params
+        * @return array
+        */
+       function getScriptParams( $params ) {
+               return [
+                       'width' => $params['width'],
+                       'page' => $params['page'],
+               ];
+       }
+
+       /**
+        * @param File $image
+        * @param string $dstPath
+        * @param string $dstUrl
+        * @param array $params
+        * @param int $flags
+        * @return MediaTransformError|ThumbnailImage|TransformParameterError
+        */
+       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+               global $wgDjvuRenderer, $wgDjvuPostProcessor;
+
+               if ( !$this->normaliseParams( $image, $params ) ) {
+                       return new TransformParameterError( $params );
+               }
+               $width = $params['width'];
+               $height = $params['height'];
+               $page = $params['page'];
+
+               if ( $flags & self::TRANSFORM_LATER ) {
+                       $params = [
+                               'width' => $width,
+                               'height' => $height,
+                               'page' => $page
+                       ];
+
+                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
+               }
+
+               if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
+                       return new MediaTransformError(
+                               'thumbnail_error',
+                               $width,
+                               $height,
+                               wfMessage( 'thumbnail_dest_directory' )
+                       );
+               }
+
+               // Get local copy source for shell scripts
+               // Thumbnail extraction is very inefficient for large files.
+               // Provide a way to pool count limit the number of downloaders.
+               if ( $image->getSize() >= 1e7 ) { // 10MB
+                       $work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $image->getName() ),
+                               [
+                                       'doWork' => function () use ( $image ) {
+                                               return $image->getLocalRefPath();
+                                       }
+                               ]
+                       );
+                       $srcPath = $work->execute();
+               } else {
+                       $srcPath = $image->getLocalRefPath();
+               }
+
+               if ( $srcPath === false ) { // Failed to get local copy
+                       wfDebugLog( 'thumbnail',
+                               sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
+                                       wfHostname(), $image->getName() ) );
+
+                       return new MediaTransformError( 'thumbnail_error',
+                               $params['width'], $params['height'],
+                               wfMessage( 'filemissing' )
+                       );
+               }
+
+               # Use a subshell (brackets) to aggregate stderr from both pipeline commands
+               # before redirecting it to the overall stdout. This works in both Linux and Windows XP.
+               $cmd = '(' . wfEscapeShellArg(
+                       $wgDjvuRenderer,
+                       "-format=ppm",
+                       "-page={$page}",
+                       "-size={$params['physicalWidth']}x{$params['physicalHeight']}",
+                       $srcPath );
+               if ( $wgDjvuPostProcessor ) {
+                       $cmd .= " | {$wgDjvuPostProcessor}";
+               }
+               $cmd .= ' > ' . wfEscapeShellArg( $dstPath ) . ') 2>&1';
+               wfDebug( __METHOD__ . ": $cmd\n" );
+               $retval = '';
+               $err = wfShellExec( $cmd, $retval );
+
+               $removed = $this->removeBadFile( $dstPath, $retval );
+               if ( $retval != 0 || $removed ) {
+                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
+                       return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
+               } else {
+                       $params = [
+                               'width' => $width,
+                               'height' => $height,
+                               'page' => $page
+                       ];
+
+                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
+               }
+       }
+
+       /**
+        * Cache an instance of DjVuImage in an Image object, return that instance
+        *
+        * @param File|FSFile $image
+        * @param string $path
+        * @return DjVuImage
+        */
+       function getDjVuImage( $image, $path ) {
+               if ( !$image ) {
+                       $deja = new DjVuImage( $path );
+               } elseif ( !isset( $image->dejaImage ) ) {
+                       $deja = $image->dejaImage = new DjVuImage( $path );
+               } else {
+                       $deja = $image->dejaImage;
+               }
+
+               return $deja;
+       }
+
+       /**
+        * Get metadata, unserializing it if neccessary.
+        *
+        * @param File $file The DjVu file in question
+        * @return string XML metadata as a string.
+        * @throws MWException
+        */
+       private function getUnserializedMetadata( File $file ) {
+               $metadata = $file->getMetadata();
+               if ( substr( $metadata, 0, 3 ) === '<?xml' ) {
+                       // Old style. Not serialized but instead just a raw string of XML.
+                       return $metadata;
+               }
+
+               Wikimedia\suppressWarnings();
+               $unser = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+               if ( is_array( $unser ) ) {
+                       if ( isset( $unser['error'] ) ) {
+                               return false;
+                       } elseif ( isset( $unser['xml'] ) ) {
+                               return $unser['xml'];
+                       } else {
+                               // Should never ever reach here.
+                               throw new MWException( "Error unserializing DjVu metadata." );
+                       }
+               }
+
+               // unserialize failed. Guess it wasn't really serialized after all,
+               return $metadata;
+       }
+
+       /**
+        * Cache a document tree for the DjVu XML metadata
+        * @param File $image
+        * @param bool $gettext DOCUMENT (Default: false)
+        * @return bool|SimpleXMLElement
+        */
+       public function getMetaTree( $image, $gettext = false ) {
+               if ( $gettext && isset( $image->djvuTextTree ) ) {
+                       return $image->djvuTextTree;
+               }
+               if ( !$gettext && isset( $image->dejaMetaTree ) ) {
+                       return $image->dejaMetaTree;
+               }
+
+               $metadata = $this->getUnserializedMetadata( $image );
+               if ( !$this->isMetadataValid( $image, $metadata ) ) {
+                       wfDebug( "DjVu XML metadata is invalid or missing, should have been fixed in upgradeRow\n" );
+
+                       return false;
+               }
+
+               $trees = $this->extractTreesFromMetadata( $metadata );
+               $image->djvuTextTree = $trees['TextTree'];
+               $image->dejaMetaTree = $trees['MetaTree'];
+
+               if ( $gettext ) {
+                       return $image->djvuTextTree;
+               } else {
+                       return $image->dejaMetaTree;
+               }
+       }
+
+       /**
+        * Extracts metadata and text trees from metadata XML in string form
+        * @param string $metadata XML metadata as a string
+        * @return array
+        */
+       protected function extractTreesFromMetadata( $metadata ) {
+               Wikimedia\suppressWarnings();
+               try {
+                       // Set to false rather than null to avoid further attempts
+                       $metaTree = false;
+                       $textTree = false;
+                       $tree = new SimpleXMLElement( $metadata, LIBXML_PARSEHUGE );
+                       if ( $tree->getName() == 'mw-djvu' ) {
+                               /** @var SimpleXMLElement $b */
+                               foreach ( $tree->children() as $b ) {
+                                       if ( $b->getName() == 'DjVuTxt' ) {
+                                               // @todo File::djvuTextTree and File::dejaMetaTree are declared
+                                               // dynamically. Add a public File::$data to facilitate this?
+                                               $textTree = $b;
+                                       } elseif ( $b->getName() == 'DjVuXML' ) {
+                                               $metaTree = $b;
+                                       }
+                               }
+                       } else {
+                               $metaTree = $tree;
+                       }
+               } catch ( Exception $e ) {
+                       wfDebug( "Bogus multipage XML metadata\n" );
+               }
+               Wikimedia\restoreWarnings();
+
+               return [ 'MetaTree' => $metaTree, 'TextTree' => $textTree ];
+       }
+
+       function getImageSize( $image, $path ) {
+               return $this->getDjVuImage( $image, $path )->getImageSize();
+       }
+
+       function getThumbType( $ext, $mime, $params = null ) {
+               global $wgDjvuOutputExtension;
+               static $mime;
+               if ( !isset( $mime ) ) {
+                       $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
+                       $mime = $magic->guessTypesForExtension( $wgDjvuOutputExtension );
+               }
+
+               return [ $wgDjvuOutputExtension, $mime ];
+       }
+
+       function getMetadata( $image, $path ) {
+               wfDebug( "Getting DjVu metadata for $path\n" );
+
+               $xml = $this->getDjVuImage( $image, $path )->retrieveMetaData();
+               if ( $xml === false ) {
+                       // Special value so that we don't repetitively try and decode a broken file.
+                       return serialize( [ 'error' => 'Error extracting metadata' ] );
+               } else {
+                       return serialize( [ 'xml' => $xml ] );
+               }
+       }
+
+       function getMetadataType( $image ) {
+               return 'djvuxml';
+       }
+
+       function isMetadataValid( $image, $metadata ) {
+               return !empty( $metadata ) && $metadata != serialize( [] );
+       }
+
+       function pageCount( File $image ) {
+               $info = $this->getDimensionInfo( $image );
+
+               return $info ? $info['pageCount'] : false;
+       }
+
+       function getPageDimensions( File $image, $page ) {
+               $index = $page - 1; // MW starts pages at 1
+
+               $info = $this->getDimensionInfo( $image );
+               if ( $info && isset( $info['dimensionsByPage'][$index] ) ) {
+                       return $info['dimensionsByPage'][$index];
+               }
+
+               return false;
+       }
+
+       protected function getDimensionInfo( File $file ) {
+               $cache = ObjectCache::getMainWANInstance();
+               return $cache->getWithSetCallback(
+                       $cache->makeKey( 'file-djvu', 'dimensions', $file->getSha1() ),
+                       $cache::TTL_INDEFINITE,
+                       function () use ( $file ) {
+                               $tree = $this->getMetaTree( $file );
+                               return $this->getDimensionInfoFromMetaTree( $tree );
+                       },
+                       [ 'pcTTL' => $cache::TTL_INDEFINITE ]
+               );
+       }
+
+       /**
+        * Given an XML metadata tree, returns dimension information about the document
+        * @param bool|SimpleXMLElement $metatree The file's XML metadata tree
+        * @return bool|array
+        */
+       protected function getDimensionInfoFromMetaTree( $metatree ) {
+               if ( !$metatree ) {
+                       return false;
+               }
+
+               $dimsByPage = [];
+               $count = count( $metatree->xpath( '//OBJECT' ) );
+               for ( $i = 0; $i < $count; $i++ ) {
+                       $o = $metatree->BODY[0]->OBJECT[$i];
+                       if ( $o ) {
+                               $dimsByPage[$i] = [
+                                       'width' => (int)$o['width'],
+                                       'height' => (int)$o['height'],
+                               ];
+                       } else {
+                               $dimsByPage[$i] = false;
+                       }
+               }
+
+               return [ 'pageCount' => $count, 'dimensionsByPage' => $dimsByPage ];
+       }
+
+       /**
+        * @param File $image
+        * @param int $page Page number to get information for
+        * @return bool|string Page text or false when no text found.
+        */
+       function getPageText( File $image, $page ) {
+               $tree = $this->getMetaTree( $image, true );
+               if ( !$tree ) {
+                       return false;
+               }
+
+               $o = $tree->BODY[0]->PAGE[$page - 1];
+               if ( $o ) {
+                       $txt = $o['value'];
+
+                       return $txt;
+               } else {
+                       return false;
+               }
+       }
+}
diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php
deleted file mode 100644 (file)
index 4267210..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-<?php
-/**
- * Handler for bitmap images with exif metadata.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Stuff specific to JPEG and (built-in) TIFF handler.
- * All metadata related, since both JPEG and TIFF support Exif.
- *
- * @ingroup Media
- */
-class ExifBitmapHandler extends BitmapHandler {
-       const BROKEN_FILE = '-1'; // error extracting metadata
-       const OLD_BROKEN_FILE = '0'; // outdated error extracting metadata.
-
-       function convertMetadataVersion( $metadata, $version = 1 ) {
-               // basically flattens arrays.
-               $version = intval( explode( ';', $version, 2 )[0] );
-               if ( $version < 1 || $version >= 2 ) {
-                       return $metadata;
-               }
-
-               $avoidHtml = true;
-
-               if ( !is_array( $metadata ) ) {
-                       $metadata = unserialize( $metadata );
-               }
-               if ( !isset( $metadata['MEDIAWIKI_EXIF_VERSION'] ) || $metadata['MEDIAWIKI_EXIF_VERSION'] != 2 ) {
-                       return $metadata;
-               }
-
-               // Treat Software as a special case because in can contain
-               // an array of (SoftwareName, Version).
-               if ( isset( $metadata['Software'] )
-                       && is_array( $metadata['Software'] )
-                       && is_array( $metadata['Software'][0] )
-                       && isset( $metadata['Software'][0][0] )
-                       && isset( $metadata['Software'][0][1] )
-               ) {
-                       $metadata['Software'] = $metadata['Software'][0][0] . ' (Version '
-                               . $metadata['Software'][0][1] . ')';
-               }
-
-               $formatter = new FormatMetadata;
-
-               // ContactInfo also has to be dealt with specially
-               if ( isset( $metadata['Contact'] ) ) {
-                       $metadata['Contact'] =
-                               $formatter->collapseContactInfo(
-                                       $metadata['Contact'] );
-               }
-
-               foreach ( $metadata as &$val ) {
-                       if ( is_array( $val ) ) {
-                               $val = $formatter->flattenArrayReal( $val, 'ul', $avoidHtml );
-                       }
-               }
-               $metadata['MEDIAWIKI_EXIF_VERSION'] = 1;
-
-               return $metadata;
-       }
-
-       /**
-        * @param File $image
-        * @param array $metadata
-        * @return bool|int
-        */
-       function isMetadataValid( $image, $metadata ) {
-               global $wgShowEXIF;
-               if ( !$wgShowEXIF ) {
-                       # Metadata disabled and so an empty field is expected
-                       return self::METADATA_GOOD;
-               }
-               if ( $metadata === self::OLD_BROKEN_FILE ) {
-                       # Old special value indicating that there is no Exif data in the file.
-                       # or that there was an error well extracting the metadata.
-                       wfDebug( __METHOD__ . ": back-compat version\n" );
-
-                       return self::METADATA_COMPATIBLE;
-               }
-               if ( $metadata === self::BROKEN_FILE ) {
-                       return self::METADATA_GOOD;
-               }
-               Wikimedia\suppressWarnings();
-               $exif = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-               if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] )
-                       || $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version()
-               ) {
-                       if ( isset( $exif['MEDIAWIKI_EXIF_VERSION'] )
-                               && $exif['MEDIAWIKI_EXIF_VERSION'] == 1
-                       ) {
-                               // back-compatible but old
-                               wfDebug( __METHOD__ . ": back-compat version\n" );
-
-                               return self::METADATA_COMPATIBLE;
-                       }
-                       # Wrong (non-compatible) version
-                       wfDebug( __METHOD__ . ": wrong version\n" );
-
-                       return self::METADATA_BAD;
-               }
-
-               return self::METADATA_GOOD;
-       }
-
-       /**
-        * @param File $image
-        * @param bool|IContextSource $context Context to use (optional)
-        * @return array|bool
-        */
-       function formatMetadata( $image, $context = false ) {
-               $meta = $this->getCommonMetaArray( $image );
-               if ( count( $meta ) === 0 ) {
-                       return false;
-               }
-
-               return $this->formatMetadataHelper( $meta, $context );
-       }
-
-       public function getCommonMetaArray( File $file ) {
-               $metadata = $file->getMetadata();
-               if ( $metadata === self::OLD_BROKEN_FILE
-                       || $metadata === self::BROKEN_FILE
-                       || $this->isMetadataValid( $file, $metadata ) === self::METADATA_BAD
-               ) {
-                       // So we don't try and display metadata from PagedTiffHandler
-                       // for example when using InstantCommons.
-                       return [];
-               }
-
-               $exif = unserialize( $metadata );
-               if ( !$exif ) {
-                       return [];
-               }
-               unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
-
-               return $exif;
-       }
-
-       function getMetadataType( $image ) {
-               return 'exif';
-       }
-
-       /**
-        * Wrapper for base classes ImageHandler::getImageSize() that checks for
-        * rotation reported from metadata and swaps the sizes to match.
-        *
-        * @param File|FSFile $image
-        * @param string $path
-        * @return array
-        */
-       function getImageSize( $image, $path ) {
-               $gis = parent::getImageSize( $image, $path );
-
-               // Don't just call $image->getMetadata(); FSFile::getPropsFromPath() calls us with a bogus object.
-               // This may mean we read EXIF data twice on initial upload.
-               if ( $this->autoRotateEnabled() ) {
-                       $meta = $this->getMetadata( $image, $path );
-                       $rotation = $this->getRotationForExif( $meta );
-               } else {
-                       $rotation = 0;
-               }
-
-               if ( $rotation == 90 || $rotation == 270 ) {
-                       $width = $gis[0];
-                       $gis[0] = $gis[1];
-                       $gis[1] = $width;
-               }
-
-               return $gis;
-       }
-
-       /**
-        * On supporting image formats, try to read out the low-level orientation
-        * of the file and return the angle that the file needs to be rotated to
-        * be viewed.
-        *
-        * This information is only useful when manipulating the original file;
-        * the width and height we normally work with is logical, and will match
-        * any produced output views.
-        *
-        * @param File $file
-        * @return int 0, 90, 180 or 270
-        */
-       public function getRotation( $file ) {
-               if ( !$this->autoRotateEnabled() ) {
-                       return 0;
-               }
-
-               $data = $file->getMetadata();
-
-               return $this->getRotationForExif( $data );
-       }
-
-       /**
-        * Given a chunk of serialized Exif metadata, return the orientation as
-        * degrees of rotation.
-        *
-        * @param string $data
-        * @return int 0, 90, 180 or 270
-        * @todo FIXME: Orientation can include flipping as well; see if this is an issue!
-        */
-       protected function getRotationForExif( $data ) {
-               if ( !$data ) {
-                       return 0;
-               }
-               Wikimedia\suppressWarnings();
-               $data = unserialize( $data );
-               Wikimedia\restoreWarnings();
-               if ( isset( $data['Orientation'] ) ) {
-                       # See http://sylvana.net/jpegcrop/exif_orientation.html
-                       switch ( $data['Orientation'] ) {
-                               case 8:
-                                       return 90;
-                               case 3:
-                                       return 180;
-                               case 6:
-                                       return 270;
-                               default:
-                                       return 0;
-                       }
-               }
-
-               return 0;
-       }
-}
diff --git a/includes/media/ExifBitmapHandler.php b/includes/media/ExifBitmapHandler.php
new file mode 100644 (file)
index 0000000..4267210
--- /dev/null
@@ -0,0 +1,245 @@
+<?php
+/**
+ * Handler for bitmap images with exif metadata.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Stuff specific to JPEG and (built-in) TIFF handler.
+ * All metadata related, since both JPEG and TIFF support Exif.
+ *
+ * @ingroup Media
+ */
+class ExifBitmapHandler extends BitmapHandler {
+       const BROKEN_FILE = '-1'; // error extracting metadata
+       const OLD_BROKEN_FILE = '0'; // outdated error extracting metadata.
+
+       function convertMetadataVersion( $metadata, $version = 1 ) {
+               // basically flattens arrays.
+               $version = intval( explode( ';', $version, 2 )[0] );
+               if ( $version < 1 || $version >= 2 ) {
+                       return $metadata;
+               }
+
+               $avoidHtml = true;
+
+               if ( !is_array( $metadata ) ) {
+                       $metadata = unserialize( $metadata );
+               }
+               if ( !isset( $metadata['MEDIAWIKI_EXIF_VERSION'] ) || $metadata['MEDIAWIKI_EXIF_VERSION'] != 2 ) {
+                       return $metadata;
+               }
+
+               // Treat Software as a special case because in can contain
+               // an array of (SoftwareName, Version).
+               if ( isset( $metadata['Software'] )
+                       && is_array( $metadata['Software'] )
+                       && is_array( $metadata['Software'][0] )
+                       && isset( $metadata['Software'][0][0] )
+                       && isset( $metadata['Software'][0][1] )
+               ) {
+                       $metadata['Software'] = $metadata['Software'][0][0] . ' (Version '
+                               . $metadata['Software'][0][1] . ')';
+               }
+
+               $formatter = new FormatMetadata;
+
+               // ContactInfo also has to be dealt with specially
+               if ( isset( $metadata['Contact'] ) ) {
+                       $metadata['Contact'] =
+                               $formatter->collapseContactInfo(
+                                       $metadata['Contact'] );
+               }
+
+               foreach ( $metadata as &$val ) {
+                       if ( is_array( $val ) ) {
+                               $val = $formatter->flattenArrayReal( $val, 'ul', $avoidHtml );
+                       }
+               }
+               $metadata['MEDIAWIKI_EXIF_VERSION'] = 1;
+
+               return $metadata;
+       }
+
+       /**
+        * @param File $image
+        * @param array $metadata
+        * @return bool|int
+        */
+       function isMetadataValid( $image, $metadata ) {
+               global $wgShowEXIF;
+               if ( !$wgShowEXIF ) {
+                       # Metadata disabled and so an empty field is expected
+                       return self::METADATA_GOOD;
+               }
+               if ( $metadata === self::OLD_BROKEN_FILE ) {
+                       # Old special value indicating that there is no Exif data in the file.
+                       # or that there was an error well extracting the metadata.
+                       wfDebug( __METHOD__ . ": back-compat version\n" );
+
+                       return self::METADATA_COMPATIBLE;
+               }
+               if ( $metadata === self::BROKEN_FILE ) {
+                       return self::METADATA_GOOD;
+               }
+               Wikimedia\suppressWarnings();
+               $exif = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+               if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] )
+                       || $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version()
+               ) {
+                       if ( isset( $exif['MEDIAWIKI_EXIF_VERSION'] )
+                               && $exif['MEDIAWIKI_EXIF_VERSION'] == 1
+                       ) {
+                               // back-compatible but old
+                               wfDebug( __METHOD__ . ": back-compat version\n" );
+
+                               return self::METADATA_COMPATIBLE;
+                       }
+                       # Wrong (non-compatible) version
+                       wfDebug( __METHOD__ . ": wrong version\n" );
+
+                       return self::METADATA_BAD;
+               }
+
+               return self::METADATA_GOOD;
+       }
+
+       /**
+        * @param File $image
+        * @param bool|IContextSource $context Context to use (optional)
+        * @return array|bool
+        */
+       function formatMetadata( $image, $context = false ) {
+               $meta = $this->getCommonMetaArray( $image );
+               if ( count( $meta ) === 0 ) {
+                       return false;
+               }
+
+               return $this->formatMetadataHelper( $meta, $context );
+       }
+
+       public function getCommonMetaArray( File $file ) {
+               $metadata = $file->getMetadata();
+               if ( $metadata === self::OLD_BROKEN_FILE
+                       || $metadata === self::BROKEN_FILE
+                       || $this->isMetadataValid( $file, $metadata ) === self::METADATA_BAD
+               ) {
+                       // So we don't try and display metadata from PagedTiffHandler
+                       // for example when using InstantCommons.
+                       return [];
+               }
+
+               $exif = unserialize( $metadata );
+               if ( !$exif ) {
+                       return [];
+               }
+               unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
+
+               return $exif;
+       }
+
+       function getMetadataType( $image ) {
+               return 'exif';
+       }
+
+       /**
+        * Wrapper for base classes ImageHandler::getImageSize() that checks for
+        * rotation reported from metadata and swaps the sizes to match.
+        *
+        * @param File|FSFile $image
+        * @param string $path
+        * @return array
+        */
+       function getImageSize( $image, $path ) {
+               $gis = parent::getImageSize( $image, $path );
+
+               // Don't just call $image->getMetadata(); FSFile::getPropsFromPath() calls us with a bogus object.
+               // This may mean we read EXIF data twice on initial upload.
+               if ( $this->autoRotateEnabled() ) {
+                       $meta = $this->getMetadata( $image, $path );
+                       $rotation = $this->getRotationForExif( $meta );
+               } else {
+                       $rotation = 0;
+               }
+
+               if ( $rotation == 90 || $rotation == 270 ) {
+                       $width = $gis[0];
+                       $gis[0] = $gis[1];
+                       $gis[1] = $width;
+               }
+
+               return $gis;
+       }
+
+       /**
+        * On supporting image formats, try to read out the low-level orientation
+        * of the file and return the angle that the file needs to be rotated to
+        * be viewed.
+        *
+        * This information is only useful when manipulating the original file;
+        * the width and height we normally work with is logical, and will match
+        * any produced output views.
+        *
+        * @param File $file
+        * @return int 0, 90, 180 or 270
+        */
+       public function getRotation( $file ) {
+               if ( !$this->autoRotateEnabled() ) {
+                       return 0;
+               }
+
+               $data = $file->getMetadata();
+
+               return $this->getRotationForExif( $data );
+       }
+
+       /**
+        * Given a chunk of serialized Exif metadata, return the orientation as
+        * degrees of rotation.
+        *
+        * @param string $data
+        * @return int 0, 90, 180 or 270
+        * @todo FIXME: Orientation can include flipping as well; see if this is an issue!
+        */
+       protected function getRotationForExif( $data ) {
+               if ( !$data ) {
+                       return 0;
+               }
+               Wikimedia\suppressWarnings();
+               $data = unserialize( $data );
+               Wikimedia\restoreWarnings();
+               if ( isset( $data['Orientation'] ) ) {
+                       # See http://sylvana.net/jpegcrop/exif_orientation.html
+                       switch ( $data['Orientation'] ) {
+                               case 8:
+                                       return 90;
+                               case 3:
+                                       return 180;
+                               case 6:
+                                       return 270;
+                               default:
+                                       return 0;
+                       }
+               }
+
+               return 0;
+       }
+}
diff --git a/includes/media/GIF.php b/includes/media/GIF.php
deleted file mode 100644 (file)
index d65f872..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-<?php
-/**
- * Handler for GIF images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for GIF images.
- *
- * @ingroup Media
- */
-class GIFHandler extends BitmapHandler {
-       const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
-
-       function getMetadata( $image, $filename ) {
-               try {
-                       $parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
-               } catch ( Exception $e ) {
-                       // Broken file?
-                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
-
-                       return self::BROKEN_FILE;
-               }
-
-               return serialize( $parsedGIFMetadata );
-       }
-
-       /**
-        * @param File $image
-        * @param bool|IContextSource $context Context to use (optional)
-        * @return array|bool
-        */
-       function formatMetadata( $image, $context = false ) {
-               $meta = $this->getCommonMetaArray( $image );
-               if ( count( $meta ) === 0 ) {
-                       return false;
-               }
-
-               return $this->formatMetadataHelper( $meta, $context );
-       }
-
-       /**
-        * Return the standard metadata elements for #filemetadata parser func.
-        * @param File $image
-        * @return array|bool
-        */
-       public function getCommonMetaArray( File $image ) {
-               $meta = $image->getMetadata();
-
-               if ( !$meta ) {
-                       return [];
-               }
-               $meta = unserialize( $meta );
-               if ( !isset( $meta['metadata'] ) ) {
-                       return [];
-               }
-               unset( $meta['metadata']['_MW_GIF_VERSION'] );
-
-               return $meta['metadata'];
-       }
-
-       /**
-        * @todo Add unit tests
-        *
-        * @param File $image
-        * @return bool
-        */
-       function getImageArea( $image ) {
-               $ser = $image->getMetadata();
-               if ( $ser ) {
-                       $metadata = unserialize( $ser );
-
-                       return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
-               } else {
-                       return $image->getWidth() * $image->getHeight();
-               }
-       }
-
-       /**
-        * @param File $image
-        * @return bool
-        */
-       function isAnimatedImage( $image ) {
-               $ser = $image->getMetadata();
-               if ( $ser ) {
-                       $metadata = unserialize( $ser );
-                       if ( $metadata['frameCount'] > 1 ) {
-                               return true;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * We cannot animate thumbnails that are bigger than a particular size
-        * @param File $file
-        * @return bool
-        */
-       function canAnimateThumbnail( $file ) {
-               global $wgMaxAnimatedGifArea;
-               $answer = $this->getImageArea( $file ) <= $wgMaxAnimatedGifArea;
-
-               return $answer;
-       }
-
-       function getMetadataType( $image ) {
-               return 'parsed-gif';
-       }
-
-       function isMetadataValid( $image, $metadata ) {
-               if ( $metadata === self::BROKEN_FILE ) {
-                       // Do not repetitivly regenerate metadata on broken file.
-                       return self::METADATA_GOOD;
-               }
-
-               Wikimedia\suppressWarnings();
-               $data = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-
-               if ( !$data || !is_array( $data ) ) {
-                       wfDebug( __METHOD__ . " invalid GIF metadata\n" );
-
-                       return self::METADATA_BAD;
-               }
-
-               if ( !isset( $data['metadata']['_MW_GIF_VERSION'] )
-                       || $data['metadata']['_MW_GIF_VERSION'] != GIFMetadataExtractor::VERSION
-               ) {
-                       wfDebug( __METHOD__ . " old but compatible GIF metadata\n" );
-
-                       return self::METADATA_COMPATIBLE;
-               }
-
-               return self::METADATA_GOOD;
-       }
-
-       /**
-        * @param File $image
-        * @return string
-        */
-       function getLongDesc( $image ) {
-               global $wgLang;
-
-               $original = parent::getLongDesc( $image );
-
-               Wikimedia\suppressWarnings();
-               $metadata = unserialize( $image->getMetadata() );
-               Wikimedia\restoreWarnings();
-
-               if ( !$metadata || $metadata['frameCount'] <= 1 ) {
-                       return $original;
-               }
-
-               /* Preserve original image info string, but strip the last char ')' so we can add even more */
-               $info = [];
-               $info[] = $original;
-
-               if ( $metadata['looped'] ) {
-                       $info[] = wfMessage( 'file-info-gif-looped' )->parse();
-               }
-
-               if ( $metadata['frameCount'] > 1 ) {
-                       $info[] = wfMessage( 'file-info-gif-frames' )->numParams( $metadata['frameCount'] )->parse();
-               }
-
-               if ( $metadata['duration'] ) {
-                       $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
-               }
-
-               return $wgLang->commaList( $info );
-       }
-
-       /**
-        * Return the duration of the GIF file.
-        *
-        * Shown in the &query=imageinfo&iiprop=size api query.
-        *
-        * @param File $file
-        * @return float The duration of the file.
-        */
-       public function getLength( $file ) {
-               $serMeta = $file->getMetadata();
-               Wikimedia\suppressWarnings();
-               $metadata = unserialize( $serMeta );
-               Wikimedia\restoreWarnings();
-
-               if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) {
-                       return 0.0;
-               } else {
-                       return (float)$metadata['duration'];
-               }
-       }
-}
diff --git a/includes/media/GIFHandler.php b/includes/media/GIFHandler.php
new file mode 100644 (file)
index 0000000..d65f872
--- /dev/null
@@ -0,0 +1,211 @@
+<?php
+/**
+ * Handler for GIF images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for GIF images.
+ *
+ * @ingroup Media
+ */
+class GIFHandler extends BitmapHandler {
+       const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
+
+       function getMetadata( $image, $filename ) {
+               try {
+                       $parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
+               } catch ( Exception $e ) {
+                       // Broken file?
+                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
+                       return self::BROKEN_FILE;
+               }
+
+               return serialize( $parsedGIFMetadata );
+       }
+
+       /**
+        * @param File $image
+        * @param bool|IContextSource $context Context to use (optional)
+        * @return array|bool
+        */
+       function formatMetadata( $image, $context = false ) {
+               $meta = $this->getCommonMetaArray( $image );
+               if ( count( $meta ) === 0 ) {
+                       return false;
+               }
+
+               return $this->formatMetadataHelper( $meta, $context );
+       }
+
+       /**
+        * Return the standard metadata elements for #filemetadata parser func.
+        * @param File $image
+        * @return array|bool
+        */
+       public function getCommonMetaArray( File $image ) {
+               $meta = $image->getMetadata();
+
+               if ( !$meta ) {
+                       return [];
+               }
+               $meta = unserialize( $meta );
+               if ( !isset( $meta['metadata'] ) ) {
+                       return [];
+               }
+               unset( $meta['metadata']['_MW_GIF_VERSION'] );
+
+               return $meta['metadata'];
+       }
+
+       /**
+        * @todo Add unit tests
+        *
+        * @param File $image
+        * @return bool
+        */
+       function getImageArea( $image ) {
+               $ser = $image->getMetadata();
+               if ( $ser ) {
+                       $metadata = unserialize( $ser );
+
+                       return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
+               } else {
+                       return $image->getWidth() * $image->getHeight();
+               }
+       }
+
+       /**
+        * @param File $image
+        * @return bool
+        */
+       function isAnimatedImage( $image ) {
+               $ser = $image->getMetadata();
+               if ( $ser ) {
+                       $metadata = unserialize( $ser );
+                       if ( $metadata['frameCount'] > 1 ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * We cannot animate thumbnails that are bigger than a particular size
+        * @param File $file
+        * @return bool
+        */
+       function canAnimateThumbnail( $file ) {
+               global $wgMaxAnimatedGifArea;
+               $answer = $this->getImageArea( $file ) <= $wgMaxAnimatedGifArea;
+
+               return $answer;
+       }
+
+       function getMetadataType( $image ) {
+               return 'parsed-gif';
+       }
+
+       function isMetadataValid( $image, $metadata ) {
+               if ( $metadata === self::BROKEN_FILE ) {
+                       // Do not repetitivly regenerate metadata on broken file.
+                       return self::METADATA_GOOD;
+               }
+
+               Wikimedia\suppressWarnings();
+               $data = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+
+               if ( !$data || !is_array( $data ) ) {
+                       wfDebug( __METHOD__ . " invalid GIF metadata\n" );
+
+                       return self::METADATA_BAD;
+               }
+
+               if ( !isset( $data['metadata']['_MW_GIF_VERSION'] )
+                       || $data['metadata']['_MW_GIF_VERSION'] != GIFMetadataExtractor::VERSION
+               ) {
+                       wfDebug( __METHOD__ . " old but compatible GIF metadata\n" );
+
+                       return self::METADATA_COMPATIBLE;
+               }
+
+               return self::METADATA_GOOD;
+       }
+
+       /**
+        * @param File $image
+        * @return string
+        */
+       function getLongDesc( $image ) {
+               global $wgLang;
+
+               $original = parent::getLongDesc( $image );
+
+               Wikimedia\suppressWarnings();
+               $metadata = unserialize( $image->getMetadata() );
+               Wikimedia\restoreWarnings();
+
+               if ( !$metadata || $metadata['frameCount'] <= 1 ) {
+                       return $original;
+               }
+
+               /* Preserve original image info string, but strip the last char ')' so we can add even more */
+               $info = [];
+               $info[] = $original;
+
+               if ( $metadata['looped'] ) {
+                       $info[] = wfMessage( 'file-info-gif-looped' )->parse();
+               }
+
+               if ( $metadata['frameCount'] > 1 ) {
+                       $info[] = wfMessage( 'file-info-gif-frames' )->numParams( $metadata['frameCount'] )->parse();
+               }
+
+               if ( $metadata['duration'] ) {
+                       $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
+               }
+
+               return $wgLang->commaList( $info );
+       }
+
+       /**
+        * Return the duration of the GIF file.
+        *
+        * Shown in the &query=imageinfo&iiprop=size api query.
+        *
+        * @param File $file
+        * @return float The duration of the file.
+        */
+       public function getLength( $file ) {
+               $serMeta = $file->getMetadata();
+               Wikimedia\suppressWarnings();
+               $metadata = unserialize( $serMeta );
+               Wikimedia\restoreWarnings();
+
+               if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) {
+                       return 0.0;
+               } else {
+                       return (float)$metadata['duration'];
+               }
+       }
+}
diff --git a/includes/media/Jpeg.php b/includes/media/Jpeg.php
deleted file mode 100644 (file)
index 287c198..0000000
+++ /dev/null
@@ -1,290 +0,0 @@
-<?php
-/**
- * Handler for JPEG images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * JPEG specific handler.
- * Inherits most stuff from BitmapHandler, just here to do the metadata handler differently.
- *
- * Metadata stuff common to Jpeg and built-in Tiff (not PagedTiffHandler) is
- * in ExifBitmapHandler.
- *
- * @ingroup Media
- */
-class JpegHandler extends ExifBitmapHandler {
-       const SRGB_EXIF_COLOR_SPACE = 'sRGB';
-       const SRGB_ICC_PROFILE_DESCRIPTION = 'sRGB IEC61966-2.1';
-
-       function normaliseParams( $image, &$params ) {
-               if ( !parent::normaliseParams( $image, $params ) ) {
-                       return false;
-               }
-               if ( isset( $params['quality'] ) && !self::validateQuality( $params['quality'] ) ) {
-                       return false;
-               }
-               return true;
-       }
-
-       public function validateParam( $name, $value ) {
-               if ( $name === 'quality' ) {
-                       return self::validateQuality( $value );
-               } else {
-                       return parent::validateParam( $name, $value );
-               }
-       }
-
-       /** Validate and normalize quality value to be between 1 and 100 (inclusive).
-        * @param int $value Quality value, will be converted to integer or 0 if invalid
-        * @return bool True if the value is valid
-        */
-       private static function validateQuality( $value ) {
-               return $value === 'low';
-       }
-
-       public function makeParamString( $params ) {
-               // Prepend quality as "qValue-". This has to match parseParamString() below
-               $res = parent::makeParamString( $params );
-               if ( $res && isset( $params['quality'] ) ) {
-                       $res = "q{$params['quality']}-$res";
-               }
-               return $res;
-       }
-
-       public function parseParamString( $str ) {
-               // $str contains "qlow-200px" or "200px" strings because thumb.php would strip the filename
-               // first - check if the string begins with "qlow-", and if so, treat it as quality.
-               // Pass the first portion, or the whole string if "qlow-" not found, to the parent
-               // The parsing must match the makeParamString() above
-               $res = false;
-               $m = false;
-               if ( preg_match( '/q([^-]+)-(.*)$/', $str, $m ) ) {
-                       $v = $m[1];
-                       if ( self::validateQuality( $v ) ) {
-                               $res = parent::parseParamString( $m[2] );
-                               if ( $res ) {
-                                       $res['quality'] = $v;
-                               }
-                       }
-               } else {
-                       $res = parent::parseParamString( $str );
-               }
-               return $res;
-       }
-
-       function getScriptParams( $params ) {
-               $res = parent::getScriptParams( $params );
-               if ( isset( $params['quality'] ) ) {
-                       $res['quality'] = $params['quality'];
-               }
-               return $res;
-       }
-
-       function getMetadata( $image, $filename ) {
-               try {
-                       $meta = BitmapMetadataHandler::Jpeg( $filename );
-                       if ( !is_array( $meta ) ) {
-                               // This should never happen, but doesn't hurt to be paranoid.
-                               throw new MWException( 'Metadata array is not an array' );
-                       }
-                       $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
-
-                       return serialize( $meta );
-               } catch ( Exception $e ) {
-                       // BitmapMetadataHandler throws an exception in certain exceptional
-                       // cases like if file does not exist.
-                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
-
-                       /* This used to use 0 (ExifBitmapHandler::OLD_BROKEN_FILE) for the cases
-                        *   * No metadata in the file
-                        *   * Something is broken in the file.
-                        * However, if the metadata support gets expanded then you can't tell if the 0 is from
-                        * a broken file, or just no props found. A broken file is likely to stay broken, but
-                        * a file which had no props could have props once the metadata support is improved.
-                        * Thus switch to using -1 to denote only a broken file, and use an array with only
-                        * MEDIAWIKI_EXIF_VERSION to denote no props.
-                        */
-
-                       return ExifBitmapHandler::BROKEN_FILE;
-               }
-       }
-
-       /**
-        * @param File $file
-        * @param array $params Rotate parameters.
-        *    'rotation' clockwise rotation in degrees, allowed are multiples of 90
-        * @since 1.21
-        * @return bool|MediaTransformError
-        */
-       public function rotate( $file, $params ) {
-               global $wgJpegTran;
-
-               $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
-
-               if ( $wgJpegTran && is_executable( $wgJpegTran ) ) {
-                       $cmd = wfEscapeShellArg( $wgJpegTran ) .
-                               " -rotate " . wfEscapeShellArg( $rotation ) .
-                               " -outfile " . wfEscapeShellArg( $params['dstPath'] ) .
-                               " " . wfEscapeShellArg( $params['srcPath'] );
-                       wfDebug( __METHOD__ . ": running jpgtran: $cmd\n" );
-                       $retval = 0;
-                       $err = wfShellExecWithStderr( $cmd, $retval );
-                       if ( $retval !== 0 ) {
-                               $this->logErrorForExternalProcess( $retval, $err, $cmd );
-
-                               return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
-                       }
-
-                       return false;
-               } else {
-                       return parent::rotate( $file, $params );
-               }
-       }
-
-       public function supportsBucketing() {
-               return true;
-       }
-
-       public function sanitizeParamsForBucketing( $params ) {
-               $params = parent::sanitizeParamsForBucketing( $params );
-
-               // Quality needs to be cleared for bucketing. Buckets need to be default quality
-               if ( isset( $params['quality'] ) ) {
-                       unset( $params['quality'] );
-               }
-
-               return $params;
-       }
-
-       /**
-        * @inheritDoc
-        */
-       protected function transformImageMagick( $image, $params ) {
-               global $wgUseTinyRGBForJPGThumbnails;
-
-               $ret = parent::transformImageMagick( $image, $params );
-
-               if ( $ret ) {
-                       return $ret;
-               }
-
-               if ( $wgUseTinyRGBForJPGThumbnails ) {
-                       // T100976 If the profile embedded in the JPG is sRGB, swap it for the smaller
-                       // (and free) TinyRGB
-
-                       /**
-                        * We'll want to replace the color profile for JPGs:
-                        * * in the sRGB color space, or with the sRGB profile
-                        *   (other profiles will be left untouched)
-                        * * without color space or profile, in which case browsers
-                        *   should assume sRGB, but don't always do (e.g. on wide-gamut
-                        *   monitors (unless it's meant for low bandwith)
-                        * @see https://phabricator.wikimedia.org/T134498
-                        */
-                       $colorSpaces = [ self::SRGB_EXIF_COLOR_SPACE, '-' ];
-                       $profiles = [ self::SRGB_ICC_PROFILE_DESCRIPTION ];
-
-                       // we'll also add TinyRGB profile to images lacking a profile, but
-                       // only if they're not low quality (which are meant to save bandwith
-                       // and we don't want to increase the filesize by adding a profile)
-                       if ( isset( $params['quality'] ) && $params['quality'] > 30 ) {
-                               $profiles[] = '-';
-                       }
-
-                       $this->swapICCProfile(
-                               $params['dstPath'],
-                               $colorSpaces,
-                               $profiles,
-                               realpath( __DIR__ ) . '/tinyrgb.icc'
-                       );
-               }
-
-               return false;
-       }
-
-       /**
-        * Swaps an embedded ICC profile for another, if found.
-        * Depends on exiftool, no-op if not installed.
-        * @param string $filepath File to be manipulated (will be overwritten)
-        * @param array $colorSpaces Only process files with this/these Color Space(s)
-        * @param array $oldProfileStrings Exact name(s) of color profile to look for
-        *  (the one that will be replaced)
-        * @param string $profileFilepath ICC profile file to apply to the file
-        * @since 1.26
-        * @return bool
-        */
-       public function swapICCProfile( $filepath, array $colorSpaces,
-                                                                       array $oldProfileStrings, $profileFilepath
-       ) {
-               global $wgExiftool;
-
-               if ( !$wgExiftool || !is_executable( $wgExiftool ) ) {
-                       return false;
-               }
-
-               $cmd = wfEscapeShellArg( $wgExiftool,
-                       '-EXIF:ColorSpace',
-                       '-ICC_Profile:ProfileDescription',
-                       '-S',
-                       '-T',
-                       $filepath
-               );
-
-               $output = wfShellExecWithStderr( $cmd, $retval );
-
-               // Explode EXIF data into an array with [0 => Color Space, 1 => Device Model Desc]
-               $data = explode( "\t", trim( $output ) );
-
-               if ( $retval !== 0 ) {
-                       return false;
-               }
-
-               // Make a regex out of the source data to match it to an array of color
-               // spaces in a case-insensitive way
-               $colorSpaceRegex = '/'.preg_quote( $data[0], '/' ).'/i';
-               if ( empty( preg_grep( $colorSpaceRegex, $colorSpaces ) ) ) {
-                       // We can't establish that this file matches the color space, don't process it
-                       return false;
-               }
-
-               $profileRegex = '/'.preg_quote( $data[1], '/' ).'/i';
-               if ( empty( preg_grep( $profileRegex, $oldProfileStrings ) ) ) {
-                       // We can't establish that this file has the expected ICC profile, don't process it
-                       return false;
-               }
-
-               $cmd = wfEscapeShellArg( $wgExiftool,
-                       '-overwrite_original',
-                       '-icc_profile<=' . $profileFilepath,
-                       $filepath
-               );
-
-               $output = wfShellExecWithStderr( $cmd, $retval );
-
-               if ( $retval !== 0 ) {
-                       $this->logErrorForExternalProcess( $retval, $output, $cmd );
-
-                       return false;
-               }
-
-               return true;
-       }
-}
diff --git a/includes/media/JpegHandler.php b/includes/media/JpegHandler.php
new file mode 100644 (file)
index 0000000..287c198
--- /dev/null
@@ -0,0 +1,290 @@
+<?php
+/**
+ * Handler for JPEG images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * JPEG specific handler.
+ * Inherits most stuff from BitmapHandler, just here to do the metadata handler differently.
+ *
+ * Metadata stuff common to Jpeg and built-in Tiff (not PagedTiffHandler) is
+ * in ExifBitmapHandler.
+ *
+ * @ingroup Media
+ */
+class JpegHandler extends ExifBitmapHandler {
+       const SRGB_EXIF_COLOR_SPACE = 'sRGB';
+       const SRGB_ICC_PROFILE_DESCRIPTION = 'sRGB IEC61966-2.1';
+
+       function normaliseParams( $image, &$params ) {
+               if ( !parent::normaliseParams( $image, $params ) ) {
+                       return false;
+               }
+               if ( isset( $params['quality'] ) && !self::validateQuality( $params['quality'] ) ) {
+                       return false;
+               }
+               return true;
+       }
+
+       public function validateParam( $name, $value ) {
+               if ( $name === 'quality' ) {
+                       return self::validateQuality( $value );
+               } else {
+                       return parent::validateParam( $name, $value );
+               }
+       }
+
+       /** Validate and normalize quality value to be between 1 and 100 (inclusive).
+        * @param int $value Quality value, will be converted to integer or 0 if invalid
+        * @return bool True if the value is valid
+        */
+       private static function validateQuality( $value ) {
+               return $value === 'low';
+       }
+
+       public function makeParamString( $params ) {
+               // Prepend quality as "qValue-". This has to match parseParamString() below
+               $res = parent::makeParamString( $params );
+               if ( $res && isset( $params['quality'] ) ) {
+                       $res = "q{$params['quality']}-$res";
+               }
+               return $res;
+       }
+
+       public function parseParamString( $str ) {
+               // $str contains "qlow-200px" or "200px" strings because thumb.php would strip the filename
+               // first - check if the string begins with "qlow-", and if so, treat it as quality.
+               // Pass the first portion, or the whole string if "qlow-" not found, to the parent
+               // The parsing must match the makeParamString() above
+               $res = false;
+               $m = false;
+               if ( preg_match( '/q([^-]+)-(.*)$/', $str, $m ) ) {
+                       $v = $m[1];
+                       if ( self::validateQuality( $v ) ) {
+                               $res = parent::parseParamString( $m[2] );
+                               if ( $res ) {
+                                       $res['quality'] = $v;
+                               }
+                       }
+               } else {
+                       $res = parent::parseParamString( $str );
+               }
+               return $res;
+       }
+
+       function getScriptParams( $params ) {
+               $res = parent::getScriptParams( $params );
+               if ( isset( $params['quality'] ) ) {
+                       $res['quality'] = $params['quality'];
+               }
+               return $res;
+       }
+
+       function getMetadata( $image, $filename ) {
+               try {
+                       $meta = BitmapMetadataHandler::Jpeg( $filename );
+                       if ( !is_array( $meta ) ) {
+                               // This should never happen, but doesn't hurt to be paranoid.
+                               throw new MWException( 'Metadata array is not an array' );
+                       }
+                       $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+
+                       return serialize( $meta );
+               } catch ( Exception $e ) {
+                       // BitmapMetadataHandler throws an exception in certain exceptional
+                       // cases like if file does not exist.
+                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
+                       /* This used to use 0 (ExifBitmapHandler::OLD_BROKEN_FILE) for the cases
+                        *   * No metadata in the file
+                        *   * Something is broken in the file.
+                        * However, if the metadata support gets expanded then you can't tell if the 0 is from
+                        * a broken file, or just no props found. A broken file is likely to stay broken, but
+                        * a file which had no props could have props once the metadata support is improved.
+                        * Thus switch to using -1 to denote only a broken file, and use an array with only
+                        * MEDIAWIKI_EXIF_VERSION to denote no props.
+                        */
+
+                       return ExifBitmapHandler::BROKEN_FILE;
+               }
+       }
+
+       /**
+        * @param File $file
+        * @param array $params Rotate parameters.
+        *    'rotation' clockwise rotation in degrees, allowed are multiples of 90
+        * @since 1.21
+        * @return bool|MediaTransformError
+        */
+       public function rotate( $file, $params ) {
+               global $wgJpegTran;
+
+               $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360;
+
+               if ( $wgJpegTran && is_executable( $wgJpegTran ) ) {
+                       $cmd = wfEscapeShellArg( $wgJpegTran ) .
+                               " -rotate " . wfEscapeShellArg( $rotation ) .
+                               " -outfile " . wfEscapeShellArg( $params['dstPath'] ) .
+                               " " . wfEscapeShellArg( $params['srcPath'] );
+                       wfDebug( __METHOD__ . ": running jpgtran: $cmd\n" );
+                       $retval = 0;
+                       $err = wfShellExecWithStderr( $cmd, $retval );
+                       if ( $retval !== 0 ) {
+                               $this->logErrorForExternalProcess( $retval, $err, $cmd );
+
+                               return new MediaTransformError( 'thumbnail_error', 0, 0, $err );
+                       }
+
+                       return false;
+               } else {
+                       return parent::rotate( $file, $params );
+               }
+       }
+
+       public function supportsBucketing() {
+               return true;
+       }
+
+       public function sanitizeParamsForBucketing( $params ) {
+               $params = parent::sanitizeParamsForBucketing( $params );
+
+               // Quality needs to be cleared for bucketing. Buckets need to be default quality
+               if ( isset( $params['quality'] ) ) {
+                       unset( $params['quality'] );
+               }
+
+               return $params;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       protected function transformImageMagick( $image, $params ) {
+               global $wgUseTinyRGBForJPGThumbnails;
+
+               $ret = parent::transformImageMagick( $image, $params );
+
+               if ( $ret ) {
+                       return $ret;
+               }
+
+               if ( $wgUseTinyRGBForJPGThumbnails ) {
+                       // T100976 If the profile embedded in the JPG is sRGB, swap it for the smaller
+                       // (and free) TinyRGB
+
+                       /**
+                        * We'll want to replace the color profile for JPGs:
+                        * * in the sRGB color space, or with the sRGB profile
+                        *   (other profiles will be left untouched)
+                        * * without color space or profile, in which case browsers
+                        *   should assume sRGB, but don't always do (e.g. on wide-gamut
+                        *   monitors (unless it's meant for low bandwith)
+                        * @see https://phabricator.wikimedia.org/T134498
+                        */
+                       $colorSpaces = [ self::SRGB_EXIF_COLOR_SPACE, '-' ];
+                       $profiles = [ self::SRGB_ICC_PROFILE_DESCRIPTION ];
+
+                       // we'll also add TinyRGB profile to images lacking a profile, but
+                       // only if they're not low quality (which are meant to save bandwith
+                       // and we don't want to increase the filesize by adding a profile)
+                       if ( isset( $params['quality'] ) && $params['quality'] > 30 ) {
+                               $profiles[] = '-';
+                       }
+
+                       $this->swapICCProfile(
+                               $params['dstPath'],
+                               $colorSpaces,
+                               $profiles,
+                               realpath( __DIR__ ) . '/tinyrgb.icc'
+                       );
+               }
+
+               return false;
+       }
+
+       /**
+        * Swaps an embedded ICC profile for another, if found.
+        * Depends on exiftool, no-op if not installed.
+        * @param string $filepath File to be manipulated (will be overwritten)
+        * @param array $colorSpaces Only process files with this/these Color Space(s)
+        * @param array $oldProfileStrings Exact name(s) of color profile to look for
+        *  (the one that will be replaced)
+        * @param string $profileFilepath ICC profile file to apply to the file
+        * @since 1.26
+        * @return bool
+        */
+       public function swapICCProfile( $filepath, array $colorSpaces,
+                                                                       array $oldProfileStrings, $profileFilepath
+       ) {
+               global $wgExiftool;
+
+               if ( !$wgExiftool || !is_executable( $wgExiftool ) ) {
+                       return false;
+               }
+
+               $cmd = wfEscapeShellArg( $wgExiftool,
+                       '-EXIF:ColorSpace',
+                       '-ICC_Profile:ProfileDescription',
+                       '-S',
+                       '-T',
+                       $filepath
+               );
+
+               $output = wfShellExecWithStderr( $cmd, $retval );
+
+               // Explode EXIF data into an array with [0 => Color Space, 1 => Device Model Desc]
+               $data = explode( "\t", trim( $output ) );
+
+               if ( $retval !== 0 ) {
+                       return false;
+               }
+
+               // Make a regex out of the source data to match it to an array of color
+               // spaces in a case-insensitive way
+               $colorSpaceRegex = '/'.preg_quote( $data[0], '/' ).'/i';
+               if ( empty( preg_grep( $colorSpaceRegex, $colorSpaces ) ) ) {
+                       // We can't establish that this file matches the color space, don't process it
+                       return false;
+               }
+
+               $profileRegex = '/'.preg_quote( $data[1], '/' ).'/i';
+               if ( empty( preg_grep( $profileRegex, $oldProfileStrings ) ) ) {
+                       // We can't establish that this file has the expected ICC profile, don't process it
+                       return false;
+               }
+
+               $cmd = wfEscapeShellArg( $wgExiftool,
+                       '-overwrite_original',
+                       '-icc_profile<=' . $profileFilepath,
+                       $filepath
+               );
+
+               $output = wfShellExecWithStderr( $cmd, $retval );
+
+               if ( $retval !== 0 ) {
+                       $this->logErrorForExternalProcess( $retval, $output, $cmd );
+
+                       return false;
+               }
+
+               return true;
+       }
+}
diff --git a/includes/media/PNG.php b/includes/media/PNG.php
deleted file mode 100644 (file)
index 6748b26..0000000
+++ /dev/null
@@ -1,203 +0,0 @@
-<?php
-/**
- * Handler for PNG images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for PNG images.
- *
- * @ingroup Media
- */
-class PNGHandler extends BitmapHandler {
-       const BROKEN_FILE = '0';
-
-       /**
-        * @param File|FSFile $image
-        * @param string $filename
-        * @return string
-        */
-       function getMetadata( $image, $filename ) {
-               try {
-                       $metadata = BitmapMetadataHandler::PNG( $filename );
-               } catch ( Exception $e ) {
-                       // Broken file?
-                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
-
-                       return self::BROKEN_FILE;
-               }
-
-               return serialize( $metadata );
-       }
-
-       /**
-        * @param File $image
-        * @param bool|IContextSource $context Context to use (optional)
-        * @return array|bool
-        */
-       function formatMetadata( $image, $context = false ) {
-               $meta = $this->getCommonMetaArray( $image );
-               if ( count( $meta ) === 0 ) {
-                       return false;
-               }
-
-               return $this->formatMetadataHelper( $meta, $context );
-       }
-
-       /**
-        * Get a file type independent array of metadata.
-        *
-        * @param File $image
-        * @return array The metadata array
-        */
-       public function getCommonMetaArray( File $image ) {
-               $meta = $image->getMetadata();
-
-               if ( !$meta ) {
-                       return [];
-               }
-               $meta = unserialize( $meta );
-               if ( !isset( $meta['metadata'] ) ) {
-                       return [];
-               }
-               unset( $meta['metadata']['_MW_PNG_VERSION'] );
-
-               return $meta['metadata'];
-       }
-
-       /**
-        * @param File $image
-        * @return bool
-        */
-       function isAnimatedImage( $image ) {
-               $ser = $image->getMetadata();
-               if ( $ser ) {
-                       $metadata = unserialize( $ser );
-                       if ( $metadata['frameCount'] > 1 ) {
-                               return true;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * We do not support making APNG thumbnails, so always false
-        * @param File $image
-        * @return bool False
-        */
-       function canAnimateThumbnail( $image ) {
-               return false;
-       }
-
-       function getMetadataType( $image ) {
-               return 'parsed-png';
-       }
-
-       function isMetadataValid( $image, $metadata ) {
-               if ( $metadata === self::BROKEN_FILE ) {
-                       // Do not repetitivly regenerate metadata on broken file.
-                       return self::METADATA_GOOD;
-               }
-
-               Wikimedia\suppressWarnings();
-               $data = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-
-               if ( !$data || !is_array( $data ) ) {
-                       wfDebug( __METHOD__ . " invalid png metadata\n" );
-
-                       return self::METADATA_BAD;
-               }
-
-               if ( !isset( $data['metadata']['_MW_PNG_VERSION'] )
-                       || $data['metadata']['_MW_PNG_VERSION'] != PNGMetadataExtractor::VERSION
-               ) {
-                       wfDebug( __METHOD__ . " old but compatible png metadata\n" );
-
-                       return self::METADATA_COMPATIBLE;
-               }
-
-               return self::METADATA_GOOD;
-       }
-
-       /**
-        * @param File $image
-        * @return string
-        */
-       function getLongDesc( $image ) {
-               global $wgLang;
-               $original = parent::getLongDesc( $image );
-
-               Wikimedia\suppressWarnings();
-               $metadata = unserialize( $image->getMetadata() );
-               Wikimedia\restoreWarnings();
-
-               if ( !$metadata || $metadata['frameCount'] <= 0 ) {
-                       return $original;
-               }
-
-               $info = [];
-               $info[] = $original;
-
-               if ( $metadata['loopCount'] == 0 ) {
-                       $info[] = wfMessage( 'file-info-png-looped' )->parse();
-               } elseif ( $metadata['loopCount'] > 1 ) {
-                       $info[] = wfMessage( 'file-info-png-repeat' )->numParams( $metadata['loopCount'] )->parse();
-               }
-
-               if ( $metadata['frameCount'] > 0 ) {
-                       $info[] = wfMessage( 'file-info-png-frames' )->numParams( $metadata['frameCount'] )->parse();
-               }
-
-               if ( $metadata['duration'] ) {
-                       $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
-               }
-
-               return $wgLang->commaList( $info );
-       }
-
-       /**
-        * Return the duration of an APNG file.
-        *
-        * Shown in the &query=imageinfo&iiprop=size api query.
-        *
-        * @param File $file
-        * @return float The duration of the file.
-        */
-       public function getLength( $file ) {
-               $serMeta = $file->getMetadata();
-               Wikimedia\suppressWarnings();
-               $metadata = unserialize( $serMeta );
-               Wikimedia\restoreWarnings();
-
-               if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) {
-                       return 0.0;
-               } else {
-                       return (float)$metadata['duration'];
-               }
-       }
-
-       // PNGs should be easy to support, but it will need some sharpening applied
-       // and another user test to check if the perceived quality change is noticeable
-       public function supportsBucketing() {
-               return false;
-       }
-}
diff --git a/includes/media/PNGHandler.php b/includes/media/PNGHandler.php
new file mode 100644 (file)
index 0000000..6748b26
--- /dev/null
@@ -0,0 +1,203 @@
+<?php
+/**
+ * Handler for PNG images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for PNG images.
+ *
+ * @ingroup Media
+ */
+class PNGHandler extends BitmapHandler {
+       const BROKEN_FILE = '0';
+
+       /**
+        * @param File|FSFile $image
+        * @param string $filename
+        * @return string
+        */
+       function getMetadata( $image, $filename ) {
+               try {
+                       $metadata = BitmapMetadataHandler::PNG( $filename );
+               } catch ( Exception $e ) {
+                       // Broken file?
+                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
+                       return self::BROKEN_FILE;
+               }
+
+               return serialize( $metadata );
+       }
+
+       /**
+        * @param File $image
+        * @param bool|IContextSource $context Context to use (optional)
+        * @return array|bool
+        */
+       function formatMetadata( $image, $context = false ) {
+               $meta = $this->getCommonMetaArray( $image );
+               if ( count( $meta ) === 0 ) {
+                       return false;
+               }
+
+               return $this->formatMetadataHelper( $meta, $context );
+       }
+
+       /**
+        * Get a file type independent array of metadata.
+        *
+        * @param File $image
+        * @return array The metadata array
+        */
+       public function getCommonMetaArray( File $image ) {
+               $meta = $image->getMetadata();
+
+               if ( !$meta ) {
+                       return [];
+               }
+               $meta = unserialize( $meta );
+               if ( !isset( $meta['metadata'] ) ) {
+                       return [];
+               }
+               unset( $meta['metadata']['_MW_PNG_VERSION'] );
+
+               return $meta['metadata'];
+       }
+
+       /**
+        * @param File $image
+        * @return bool
+        */
+       function isAnimatedImage( $image ) {
+               $ser = $image->getMetadata();
+               if ( $ser ) {
+                       $metadata = unserialize( $ser );
+                       if ( $metadata['frameCount'] > 1 ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * We do not support making APNG thumbnails, so always false
+        * @param File $image
+        * @return bool False
+        */
+       function canAnimateThumbnail( $image ) {
+               return false;
+       }
+
+       function getMetadataType( $image ) {
+               return 'parsed-png';
+       }
+
+       function isMetadataValid( $image, $metadata ) {
+               if ( $metadata === self::BROKEN_FILE ) {
+                       // Do not repetitivly regenerate metadata on broken file.
+                       return self::METADATA_GOOD;
+               }
+
+               Wikimedia\suppressWarnings();
+               $data = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+
+               if ( !$data || !is_array( $data ) ) {
+                       wfDebug( __METHOD__ . " invalid png metadata\n" );
+
+                       return self::METADATA_BAD;
+               }
+
+               if ( !isset( $data['metadata']['_MW_PNG_VERSION'] )
+                       || $data['metadata']['_MW_PNG_VERSION'] != PNGMetadataExtractor::VERSION
+               ) {
+                       wfDebug( __METHOD__ . " old but compatible png metadata\n" );
+
+                       return self::METADATA_COMPATIBLE;
+               }
+
+               return self::METADATA_GOOD;
+       }
+
+       /**
+        * @param File $image
+        * @return string
+        */
+       function getLongDesc( $image ) {
+               global $wgLang;
+               $original = parent::getLongDesc( $image );
+
+               Wikimedia\suppressWarnings();
+               $metadata = unserialize( $image->getMetadata() );
+               Wikimedia\restoreWarnings();
+
+               if ( !$metadata || $metadata['frameCount'] <= 0 ) {
+                       return $original;
+               }
+
+               $info = [];
+               $info[] = $original;
+
+               if ( $metadata['loopCount'] == 0 ) {
+                       $info[] = wfMessage( 'file-info-png-looped' )->parse();
+               } elseif ( $metadata['loopCount'] > 1 ) {
+                       $info[] = wfMessage( 'file-info-png-repeat' )->numParams( $metadata['loopCount'] )->parse();
+               }
+
+               if ( $metadata['frameCount'] > 0 ) {
+                       $info[] = wfMessage( 'file-info-png-frames' )->numParams( $metadata['frameCount'] )->parse();
+               }
+
+               if ( $metadata['duration'] ) {
+                       $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
+               }
+
+               return $wgLang->commaList( $info );
+       }
+
+       /**
+        * Return the duration of an APNG file.
+        *
+        * Shown in the &query=imageinfo&iiprop=size api query.
+        *
+        * @param File $file
+        * @return float The duration of the file.
+        */
+       public function getLength( $file ) {
+               $serMeta = $file->getMetadata();
+               Wikimedia\suppressWarnings();
+               $metadata = unserialize( $serMeta );
+               Wikimedia\restoreWarnings();
+
+               if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) {
+                       return 0.0;
+               } else {
+                       return (float)$metadata['duration'];
+               }
+       }
+
+       // PNGs should be easy to support, but it will need some sharpening applied
+       // and another user test to check if the perceived quality change is noticeable
+       public function supportsBucketing() {
+               return false;
+       }
+}
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
deleted file mode 100644 (file)
index 9085421..0000000
+++ /dev/null
@@ -1,593 +0,0 @@
-<?php
-/**
- * Handler for SVG images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-use Wikimedia\ScopedCallback;
-
-/**
- * Handler for SVG images.
- *
- * @ingroup Media
- */
-class SvgHandler extends ImageHandler {
-       const SVG_METADATA_VERSION = 2;
-
-       /** @var array A list of metadata tags that can be converted
-        *  to the commonly used exif tags. This allows messages
-        *  to be reused, and consistent tag names for {{#formatmetadata:..}}
-        */
-       private static $metaConversion = [
-               'originalwidth' => 'ImageWidth',
-               'originalheight' => 'ImageLength',
-               'description' => 'ImageDescription',
-               'title' => 'ObjectName',
-       ];
-
-       function isEnabled() {
-               global $wgSVGConverters, $wgSVGConverter;
-               if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
-                       wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" );
-
-                       return false;
-               } else {
-                       return true;
-               }
-       }
-
-       public function mustRender( $file ) {
-               return true;
-       }
-
-       function isVectorized( $file ) {
-               return true;
-       }
-
-       /**
-        * @param File $file
-        * @return bool
-        */
-       function isAnimatedImage( $file ) {
-               # @todo Detect animated SVGs
-               $metadata = $file->getMetadata();
-               if ( $metadata ) {
-                       $metadata = $this->unpackMetadata( $metadata );
-                       if ( isset( $metadata['animated'] ) ) {
-                               return $metadata['animated'];
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Which languages (systemLanguage attribute) is supported.
-        *
-        * @note This list is not guaranteed to be exhaustive.
-        * To avoid OOM errors, we only look at first bit of a file.
-        * Thus all languages on this list are present in the file,
-        * but its possible for the file to have a language not on
-        * this list.
-        *
-        * @param File $file
-        * @return array Array of language codes, or empty if no language switching supported.
-        */
-       public function getAvailableLanguages( File $file ) {
-               $metadata = $file->getMetadata();
-               $langList = [];
-               if ( $metadata ) {
-                       $metadata = $this->unpackMetadata( $metadata );
-                       if ( isset( $metadata['translations'] ) ) {
-                               foreach ( $metadata['translations'] as $lang => $langType ) {
-                                       if ( $langType === SVGReader::LANG_FULL_MATCH ) {
-                                               $langList[] = strtolower( $lang );
-                                       }
-                               }
-                       }
-               }
-               return array_unique( $langList );
-       }
-
-       /**
-        * SVG's systemLanguage matching rules state:
-        * 'The `systemLanguage` attribute ... [e]valuates to "true" if one of the languages indicated
-        * by user preferences exactly equals one of the languages given in the value of this parameter,
-        * or if one of the languages indicated by user preferences exactly equals a prefix of one of
-        * the languages given in the value of this parameter such that the first tag character
-        * following the prefix is "-".'
-        *
-        * Return the first element of $svgLanguages that matches $userPreferredLanguage
-        *
-        * @see https://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute
-        * @param string $userPreferredLanguage
-        * @param array $svgLanguages
-        * @return string|null
-        */
-       public function getMatchedLanguage( $userPreferredLanguage, array $svgLanguages ) {
-               foreach ( $svgLanguages as $svgLang ) {
-                       if ( strcasecmp( $svgLang, $userPreferredLanguage ) === 0 ) {
-                               return $svgLang;
-                       }
-                       $trimmedSvgLang = $svgLang;
-                       while ( strpos( $trimmedSvgLang, '-' ) !== false ) {
-                               $trimmedSvgLang = substr( $trimmedSvgLang, 0, strrpos( $trimmedSvgLang, '-' ) );
-                               if ( strcasecmp( $trimmedSvgLang, $userPreferredLanguage ) === 0 ) {
-                                       return $svgLang;
-                               }
-                       }
-               }
-               return null;
-       }
-
-       /**
-        * What language to render file in if none selected
-        *
-        * @param File $file Language code
-        * @return string
-        */
-       public function getDefaultRenderLanguage( File $file ) {
-               return 'en';
-       }
-
-       /**
-        * We do not support making animated svg thumbnails
-        * @param File $file
-        * @return bool
-        */
-       function canAnimateThumbnail( $file ) {
-               return false;
-       }
-
-       /**
-        * @param File $image
-        * @param array &$params
-        * @return bool
-        */
-       function normaliseParams( $image, &$params ) {
-               global $wgSVGMaxSize;
-               if ( !parent::normaliseParams( $image, $params ) ) {
-                       return false;
-               }
-               # Don't make an image bigger than wgMaxSVGSize on the smaller side
-               if ( $params['physicalWidth'] <= $params['physicalHeight'] ) {
-                       if ( $params['physicalWidth'] > $wgSVGMaxSize ) {
-                               $srcWidth = $image->getWidth( $params['page'] );
-                               $srcHeight = $image->getHeight( $params['page'] );
-                               $params['physicalWidth'] = $wgSVGMaxSize;
-                               $params['physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize );
-                       }
-               } else {
-                       if ( $params['physicalHeight'] > $wgSVGMaxSize ) {
-                               $srcWidth = $image->getWidth( $params['page'] );
-                               $srcHeight = $image->getHeight( $params['page'] );
-                               $params['physicalWidth'] = File::scaleHeight( $srcHeight, $srcWidth, $wgSVGMaxSize );
-                               $params['physicalHeight'] = $wgSVGMaxSize;
-                       }
-               }
-
-               return true;
-       }
-
-       /**
-        * @param File $image
-        * @param string $dstPath
-        * @param string $dstUrl
-        * @param array $params
-        * @param int $flags
-        * @return bool|MediaTransformError|ThumbnailImage|TransformParameterError
-        */
-       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
-               if ( !$this->normaliseParams( $image, $params ) ) {
-                       return new TransformParameterError( $params );
-               }
-               $clientWidth = $params['width'];
-               $clientHeight = $params['height'];
-               $physicalWidth = $params['physicalWidth'];
-               $physicalHeight = $params['physicalHeight'];
-               $lang = isset( $params['lang'] ) ? $params['lang'] : $this->getDefaultRenderLanguage( $image );
-
-               if ( $flags & self::TRANSFORM_LATER ) {
-                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
-               }
-
-               $metadata = $this->unpackMetadata( $image->getMetadata() );
-               if ( isset( $metadata['error'] ) ) { // sanity check
-                       $err = wfMessage( 'svg-long-error', $metadata['error']['message'] );
-
-                       return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
-               }
-
-               if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
-                       return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
-                               wfMessage( 'thumbnail_dest_directory' ) );
-               }
-
-               $srcPath = $image->getLocalRefPath();
-               if ( $srcPath === false ) { // Failed to get local copy
-                       wfDebugLog( 'thumbnail',
-                               sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
-                                       wfHostname(), $image->getName() ) );
-
-                       return new MediaTransformError( 'thumbnail_error',
-                               $params['width'], $params['height'],
-                               wfMessage( 'filemissing' )
-                       );
-               }
-
-               // Make a temp dir with a symlink to the local copy in it.
-               // This plays well with rsvg-convert policy for external entities.
-               // https://git.gnome.org/browse/librsvg/commit/?id=f01aded72c38f0e18bc7ff67dee800e380251c8e
-               $tmpDir = wfTempDir() . '/svg_' . wfRandomString( 24 );
-               $lnPath = "$tmpDir/" . basename( $srcPath );
-               $ok = mkdir( $tmpDir, 0771 );
-               if ( !$ok ) {
-                       wfDebugLog( 'thumbnail',
-                               sprintf( 'Thumbnail failed on %s: could not create temporary directory %s',
-                                       wfHostname(), $tmpDir ) );
-                       return new MediaTransformError( 'thumbnail_error',
-                               $params['width'], $params['height'],
-                               wfMessage( 'thumbnail-temp-create' )->text()
-                       );
-               }
-               $ok = symlink( $srcPath, $lnPath );
-               /** @noinspection PhpUnusedLocalVariableInspection */
-               $cleaner = new ScopedCallback( function () use ( $tmpDir, $lnPath ) {
-                       Wikimedia\suppressWarnings();
-                       unlink( $lnPath );
-                       rmdir( $tmpDir );
-                       Wikimedia\restoreWarnings();
-               } );
-               if ( !$ok ) {
-                       wfDebugLog( 'thumbnail',
-                               sprintf( 'Thumbnail failed on %s: could not link %s to %s',
-                                       wfHostname(), $lnPath, $srcPath ) );
-                       return new MediaTransformError( 'thumbnail_error',
-                               $params['width'], $params['height'],
-                               wfMessage( 'thumbnail-temp-create' )
-                       );
-               }
-
-               $status = $this->rasterize( $lnPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
-               if ( $status === true ) {
-                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
-               } else {
-                       return $status; // MediaTransformError
-               }
-       }
-
-       /**
-        * Transform an SVG file to PNG
-        * This function can be called outside of thumbnail contexts
-        * @param string $srcPath
-        * @param string $dstPath
-        * @param string $width
-        * @param string $height
-        * @param bool|string $lang Language code of the language to render the SVG in
-        * @throws MWException
-        * @return bool|MediaTransformError
-        */
-       public function rasterize( $srcPath, $dstPath, $width, $height, $lang = false ) {
-               global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
-               $err = false;
-               $retval = '';
-               if ( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
-                       if ( is_array( $wgSVGConverters[$wgSVGConverter] ) ) {
-                               // This is a PHP callable
-                               $func = $wgSVGConverters[$wgSVGConverter][0];
-                               $args = array_merge( [ $srcPath, $dstPath, $width, $height, $lang ],
-                                       array_slice( $wgSVGConverters[$wgSVGConverter], 1 ) );
-                               if ( !is_callable( $func ) ) {
-                                       throw new MWException( "$func is not callable" );
-                               }
-                               $err = call_user_func_array( $func, $args );
-                               $retval = (bool)$err;
-                       } else {
-                               // External command
-                               $cmd = str_replace(
-                                       [ '$path/', '$width', '$height', '$input', '$output' ],
-                                       [ $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
-                                               intval( $width ),
-                                               intval( $height ),
-                                               wfEscapeShellArg( $srcPath ),
-                                               wfEscapeShellArg( $dstPath ) ],
-                                       $wgSVGConverters[$wgSVGConverter]
-                               );
-
-                               $env = [];
-                               if ( $lang !== false ) {
-                                       $env['LANG'] = $lang;
-                               }
-
-                               wfDebug( __METHOD__ . ": $cmd\n" );
-                               $err = wfShellExecWithStderr( $cmd, $retval, $env );
-                       }
-               }
-               $removed = $this->removeBadFile( $dstPath, $retval );
-               if ( $retval != 0 || $removed ) {
-                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
-                       return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
-               }
-
-               return true;
-       }
-
-       public static function rasterizeImagickExt( $srcPath, $dstPath, $width, $height ) {
-               $im = new Imagick( $srcPath );
-               $im->setImageFormat( 'png' );
-               $im->setBackgroundColor( 'transparent' );
-               $im->setImageDepth( 8 );
-
-               if ( !$im->thumbnailImage( intval( $width ), intval( $height ), /* fit */ false ) ) {
-                       return 'Could not resize image';
-               }
-               if ( !$im->writeImage( $dstPath ) ) {
-                       return "Could not write to $dstPath";
-               }
-       }
-
-       /**
-        * @param File|FSFile $file
-        * @param string $path Unused
-        * @param bool|array $metadata
-        * @return array
-        */
-       function getImageSize( $file, $path, $metadata = false ) {
-               if ( $metadata === false && $file instanceof File ) {
-                       $metadata = $file->getMetadata();
-               }
-               $metadata = $this->unpackMetadata( $metadata );
-
-               if ( isset( $metadata['width'] ) && isset( $metadata['height'] ) ) {
-                       return [ $metadata['width'], $metadata['height'], 'SVG',
-                               "width=\"{$metadata['width']}\" height=\"{$metadata['height']}\"" ];
-               } else { // error
-                       return [ 0, 0, 'SVG', "width=\"0\" height=\"0\"" ];
-               }
-       }
-
-       function getThumbType( $ext, $mime, $params = null ) {
-               return [ 'png', 'image/png' ];
-       }
-
-       /**
-        * Subtitle for the image. Different from the base
-        * class so it can be denoted that SVG's have
-        * a "nominal" resolution, and not a fixed one,
-        * as well as so animation can be denoted.
-        *
-        * @param File $file
-        * @return string
-        */
-       function getLongDesc( $file ) {
-               global $wgLang;
-
-               $metadata = $this->unpackMetadata( $file->getMetadata() );
-               if ( isset( $metadata['error'] ) ) {
-                       return wfMessage( 'svg-long-error', $metadata['error']['message'] )->text();
-               }
-
-               $size = $wgLang->formatSize( $file->getSize() );
-
-               if ( $this->isAnimatedImage( $file ) ) {
-                       $msg = wfMessage( 'svg-long-desc-animated' );
-               } else {
-                       $msg = wfMessage( 'svg-long-desc' );
-               }
-
-               $msg->numParams( $file->getWidth(), $file->getHeight() )->params( $size );
-
-               return $msg->parse();
-       }
-
-       /**
-        * @param File|FSFile $file
-        * @param string $filename
-        * @return string Serialised metadata
-        */
-       function getMetadata( $file, $filename ) {
-               $metadata = [ 'version' => self::SVG_METADATA_VERSION ];
-               try {
-                       $metadata += SVGMetadataExtractor::getMetadata( $filename );
-               } catch ( Exception $e ) { // @todo SVG specific exceptions
-                       // File not found, broken, etc.
-                       $metadata['error'] = [
-                               'message' => $e->getMessage(),
-                               'code' => $e->getCode()
-                       ];
-                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
-               }
-
-               return serialize( $metadata );
-       }
-
-       function unpackMetadata( $metadata ) {
-               Wikimedia\suppressWarnings();
-               $unser = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-               if ( isset( $unser['version'] ) && $unser['version'] == self::SVG_METADATA_VERSION ) {
-                       return $unser;
-               } else {
-                       return false;
-               }
-       }
-
-       function getMetadataType( $image ) {
-               return 'parsed-svg';
-       }
-
-       function isMetadataValid( $image, $metadata ) {
-               $meta = $this->unpackMetadata( $metadata );
-               if ( $meta === false ) {
-                       return self::METADATA_BAD;
-               }
-               if ( !isset( $meta['originalWidth'] ) ) {
-                       // Old but compatible
-                       return self::METADATA_COMPATIBLE;
-               }
-
-               return self::METADATA_GOOD;
-       }
-
-       protected function visibleMetadataFields() {
-               $fields = [ 'objectname', 'imagedescription' ];
-
-               return $fields;
-       }
-
-       /**
-        * @param File $file
-        * @param bool|IContextSource $context Context to use (optional)
-        * @return array|bool
-        */
-       function formatMetadata( $file, $context = false ) {
-               $result = [
-                       'visible' => [],
-                       'collapsed' => []
-               ];
-               $metadata = $file->getMetadata();
-               if ( !$metadata ) {
-                       return false;
-               }
-               $metadata = $this->unpackMetadata( $metadata );
-               if ( !$metadata || isset( $metadata['error'] ) ) {
-                       return false;
-               }
-
-               /* @todo Add a formatter
-               $format = new FormatSVG( $metadata );
-               $formatted = $format->getFormattedData();
-               */
-
-               // Sort fields into visible and collapsed
-               $visibleFields = $this->visibleMetadataFields();
-
-               $showMeta = false;
-               foreach ( $metadata as $name => $value ) {
-                       $tag = strtolower( $name );
-                       if ( isset( self::$metaConversion[$tag] ) ) {
-                               $tag = strtolower( self::$metaConversion[$tag] );
-                       } else {
-                               // Do not output other metadata not in list
-                               continue;
-                       }
-                       $showMeta = true;
-                       self::addMeta( $result,
-                               in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
-                               'exif',
-                               $tag,
-                               $value
-                       );
-               }
-
-               return $showMeta ? $result : false;
-       }
-
-       /**
-        * @param string $name Parameter name
-        * @param mixed $value Parameter value
-        * @return bool Validity
-        */
-       public function validateParam( $name, $value ) {
-               if ( in_array( $name, [ 'width', 'height' ] ) ) {
-                       // Reject negative heights, widths
-                       return ( $value > 0 );
-               } elseif ( $name == 'lang' ) {
-                       // Validate $code
-                       if ( $value === '' || !Language::isValidCode( $value ) ) {
-                               return false;
-                       }
-
-                       return true;
-               }
-
-               // Only lang, width and height are acceptable keys
-               return false;
-       }
-
-       /**
-        * @param array $params Name=>value pairs of parameters
-        * @return string Filename to use
-        */
-       public function makeParamString( $params ) {
-               $lang = '';
-               if ( isset( $params['lang'] ) && $params['lang'] !== 'en' ) {
-                       $lang = 'lang' . strtolower( $params['lang'] ) . '-';
-               }
-               if ( !isset( $params['width'] ) ) {
-                       return false;
-               }
-
-               return "$lang{$params['width']}px";
-       }
-
-       public function parseParamString( $str ) {
-               $m = false;
-               if ( preg_match( '/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/i', $str, $m ) ) {
-                       return [ 'width' => array_pop( $m ), 'lang' => $m[1] ];
-               } elseif ( preg_match( '/^(\d+)px$/', $str, $m ) ) {
-                       return [ 'width' => $m[1], 'lang' => 'en' ];
-               } else {
-                       return false;
-               }
-       }
-
-       public function getParamMap() {
-               return [ 'img_lang' => 'lang', 'img_width' => 'width' ];
-       }
-
-       /**
-        * @param array $params
-        * @return array
-        */
-       function getScriptParams( $params ) {
-               $scriptParams = [ 'width' => $params['width'] ];
-               if ( isset( $params['lang'] ) ) {
-                       $scriptParams['lang'] = $params['lang'];
-               }
-
-               return $scriptParams;
-       }
-
-       public function getCommonMetaArray( File $file ) {
-               $metadata = $file->getMetadata();
-               if ( !$metadata ) {
-                       return [];
-               }
-               $metadata = $this->unpackMetadata( $metadata );
-               if ( !$metadata || isset( $metadata['error'] ) ) {
-                       return [];
-               }
-               $stdMetadata = [];
-               foreach ( $metadata as $name => $value ) {
-                       $tag = strtolower( $name );
-                       if ( $tag === 'originalwidth' || $tag === 'originalheight' ) {
-                               // Skip these. In the exif metadata stuff, it is assumed these
-                               // are measured in px, which is not the case here.
-                               continue;
-                       }
-                       if ( isset( self::$metaConversion[$tag] ) ) {
-                               $tag = self::$metaConversion[$tag];
-                               $stdMetadata[$tag] = $value;
-                       }
-               }
-
-               return $stdMetadata;
-       }
-}
diff --git a/includes/media/SvgHandler.php b/includes/media/SvgHandler.php
new file mode 100644 (file)
index 0000000..9085421
--- /dev/null
@@ -0,0 +1,593 @@
+<?php
+/**
+ * Handler for SVG images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+use Wikimedia\ScopedCallback;
+
+/**
+ * Handler for SVG images.
+ *
+ * @ingroup Media
+ */
+class SvgHandler extends ImageHandler {
+       const SVG_METADATA_VERSION = 2;
+
+       /** @var array A list of metadata tags that can be converted
+        *  to the commonly used exif tags. This allows messages
+        *  to be reused, and consistent tag names for {{#formatmetadata:..}}
+        */
+       private static $metaConversion = [
+               'originalwidth' => 'ImageWidth',
+               'originalheight' => 'ImageLength',
+               'description' => 'ImageDescription',
+               'title' => 'ObjectName',
+       ];
+
+       function isEnabled() {
+               global $wgSVGConverters, $wgSVGConverter;
+               if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
+                       wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" );
+
+                       return false;
+               } else {
+                       return true;
+               }
+       }
+
+       public function mustRender( $file ) {
+               return true;
+       }
+
+       function isVectorized( $file ) {
+               return true;
+       }
+
+       /**
+        * @param File $file
+        * @return bool
+        */
+       function isAnimatedImage( $file ) {
+               # @todo Detect animated SVGs
+               $metadata = $file->getMetadata();
+               if ( $metadata ) {
+                       $metadata = $this->unpackMetadata( $metadata );
+                       if ( isset( $metadata['animated'] ) ) {
+                               return $metadata['animated'];
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Which languages (systemLanguage attribute) is supported.
+        *
+        * @note This list is not guaranteed to be exhaustive.
+        * To avoid OOM errors, we only look at first bit of a file.
+        * Thus all languages on this list are present in the file,
+        * but its possible for the file to have a language not on
+        * this list.
+        *
+        * @param File $file
+        * @return array Array of language codes, or empty if no language switching supported.
+        */
+       public function getAvailableLanguages( File $file ) {
+               $metadata = $file->getMetadata();
+               $langList = [];
+               if ( $metadata ) {
+                       $metadata = $this->unpackMetadata( $metadata );
+                       if ( isset( $metadata['translations'] ) ) {
+                               foreach ( $metadata['translations'] as $lang => $langType ) {
+                                       if ( $langType === SVGReader::LANG_FULL_MATCH ) {
+                                               $langList[] = strtolower( $lang );
+                                       }
+                               }
+                       }
+               }
+               return array_unique( $langList );
+       }
+
+       /**
+        * SVG's systemLanguage matching rules state:
+        * 'The `systemLanguage` attribute ... [e]valuates to "true" if one of the languages indicated
+        * by user preferences exactly equals one of the languages given in the value of this parameter,
+        * or if one of the languages indicated by user preferences exactly equals a prefix of one of
+        * the languages given in the value of this parameter such that the first tag character
+        * following the prefix is "-".'
+        *
+        * Return the first element of $svgLanguages that matches $userPreferredLanguage
+        *
+        * @see https://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute
+        * @param string $userPreferredLanguage
+        * @param array $svgLanguages
+        * @return string|null
+        */
+       public function getMatchedLanguage( $userPreferredLanguage, array $svgLanguages ) {
+               foreach ( $svgLanguages as $svgLang ) {
+                       if ( strcasecmp( $svgLang, $userPreferredLanguage ) === 0 ) {
+                               return $svgLang;
+                       }
+                       $trimmedSvgLang = $svgLang;
+                       while ( strpos( $trimmedSvgLang, '-' ) !== false ) {
+                               $trimmedSvgLang = substr( $trimmedSvgLang, 0, strrpos( $trimmedSvgLang, '-' ) );
+                               if ( strcasecmp( $trimmedSvgLang, $userPreferredLanguage ) === 0 ) {
+                                       return $svgLang;
+                               }
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * What language to render file in if none selected
+        *
+        * @param File $file Language code
+        * @return string
+        */
+       public function getDefaultRenderLanguage( File $file ) {
+               return 'en';
+       }
+
+       /**
+        * We do not support making animated svg thumbnails
+        * @param File $file
+        * @return bool
+        */
+       function canAnimateThumbnail( $file ) {
+               return false;
+       }
+
+       /**
+        * @param File $image
+        * @param array &$params
+        * @return bool
+        */
+       function normaliseParams( $image, &$params ) {
+               global $wgSVGMaxSize;
+               if ( !parent::normaliseParams( $image, $params ) ) {
+                       return false;
+               }
+               # Don't make an image bigger than wgMaxSVGSize on the smaller side
+               if ( $params['physicalWidth'] <= $params['physicalHeight'] ) {
+                       if ( $params['physicalWidth'] > $wgSVGMaxSize ) {
+                               $srcWidth = $image->getWidth( $params['page'] );
+                               $srcHeight = $image->getHeight( $params['page'] );
+                               $params['physicalWidth'] = $wgSVGMaxSize;
+                               $params['physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize );
+                       }
+               } else {
+                       if ( $params['physicalHeight'] > $wgSVGMaxSize ) {
+                               $srcWidth = $image->getWidth( $params['page'] );
+                               $srcHeight = $image->getHeight( $params['page'] );
+                               $params['physicalWidth'] = File::scaleHeight( $srcHeight, $srcWidth, $wgSVGMaxSize );
+                               $params['physicalHeight'] = $wgSVGMaxSize;
+                       }
+               }
+
+               return true;
+       }
+
+       /**
+        * @param File $image
+        * @param string $dstPath
+        * @param string $dstUrl
+        * @param array $params
+        * @param int $flags
+        * @return bool|MediaTransformError|ThumbnailImage|TransformParameterError
+        */
+       function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
+               if ( !$this->normaliseParams( $image, $params ) ) {
+                       return new TransformParameterError( $params );
+               }
+               $clientWidth = $params['width'];
+               $clientHeight = $params['height'];
+               $physicalWidth = $params['physicalWidth'];
+               $physicalHeight = $params['physicalHeight'];
+               $lang = isset( $params['lang'] ) ? $params['lang'] : $this->getDefaultRenderLanguage( $image );
+
+               if ( $flags & self::TRANSFORM_LATER ) {
+                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
+               }
+
+               $metadata = $this->unpackMetadata( $image->getMetadata() );
+               if ( isset( $metadata['error'] ) ) { // sanity check
+                       $err = wfMessage( 'svg-long-error', $metadata['error']['message'] );
+
+                       return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
+               }
+
+               if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
+                       return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
+                               wfMessage( 'thumbnail_dest_directory' ) );
+               }
+
+               $srcPath = $image->getLocalRefPath();
+               if ( $srcPath === false ) { // Failed to get local copy
+                       wfDebugLog( 'thumbnail',
+                               sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
+                                       wfHostname(), $image->getName() ) );
+
+                       return new MediaTransformError( 'thumbnail_error',
+                               $params['width'], $params['height'],
+                               wfMessage( 'filemissing' )
+                       );
+               }
+
+               // Make a temp dir with a symlink to the local copy in it.
+               // This plays well with rsvg-convert policy for external entities.
+               // https://git.gnome.org/browse/librsvg/commit/?id=f01aded72c38f0e18bc7ff67dee800e380251c8e
+               $tmpDir = wfTempDir() . '/svg_' . wfRandomString( 24 );
+               $lnPath = "$tmpDir/" . basename( $srcPath );
+               $ok = mkdir( $tmpDir, 0771 );
+               if ( !$ok ) {
+                       wfDebugLog( 'thumbnail',
+                               sprintf( 'Thumbnail failed on %s: could not create temporary directory %s',
+                                       wfHostname(), $tmpDir ) );
+                       return new MediaTransformError( 'thumbnail_error',
+                               $params['width'], $params['height'],
+                               wfMessage( 'thumbnail-temp-create' )->text()
+                       );
+               }
+               $ok = symlink( $srcPath, $lnPath );
+               /** @noinspection PhpUnusedLocalVariableInspection */
+               $cleaner = new ScopedCallback( function () use ( $tmpDir, $lnPath ) {
+                       Wikimedia\suppressWarnings();
+                       unlink( $lnPath );
+                       rmdir( $tmpDir );
+                       Wikimedia\restoreWarnings();
+               } );
+               if ( !$ok ) {
+                       wfDebugLog( 'thumbnail',
+                               sprintf( 'Thumbnail failed on %s: could not link %s to %s',
+                                       wfHostname(), $lnPath, $srcPath ) );
+                       return new MediaTransformError( 'thumbnail_error',
+                               $params['width'], $params['height'],
+                               wfMessage( 'thumbnail-temp-create' )
+                       );
+               }
+
+               $status = $this->rasterize( $lnPath, $dstPath, $physicalWidth, $physicalHeight, $lang );
+               if ( $status === true ) {
+                       return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
+               } else {
+                       return $status; // MediaTransformError
+               }
+       }
+
+       /**
+        * Transform an SVG file to PNG
+        * This function can be called outside of thumbnail contexts
+        * @param string $srcPath
+        * @param string $dstPath
+        * @param string $width
+        * @param string $height
+        * @param bool|string $lang Language code of the language to render the SVG in
+        * @throws MWException
+        * @return bool|MediaTransformError
+        */
+       public function rasterize( $srcPath, $dstPath, $width, $height, $lang = false ) {
+               global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
+               $err = false;
+               $retval = '';
+               if ( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
+                       if ( is_array( $wgSVGConverters[$wgSVGConverter] ) ) {
+                               // This is a PHP callable
+                               $func = $wgSVGConverters[$wgSVGConverter][0];
+                               $args = array_merge( [ $srcPath, $dstPath, $width, $height, $lang ],
+                                       array_slice( $wgSVGConverters[$wgSVGConverter], 1 ) );
+                               if ( !is_callable( $func ) ) {
+                                       throw new MWException( "$func is not callable" );
+                               }
+                               $err = call_user_func_array( $func, $args );
+                               $retval = (bool)$err;
+                       } else {
+                               // External command
+                               $cmd = str_replace(
+                                       [ '$path/', '$width', '$height', '$input', '$output' ],
+                                       [ $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
+                                               intval( $width ),
+                                               intval( $height ),
+                                               wfEscapeShellArg( $srcPath ),
+                                               wfEscapeShellArg( $dstPath ) ],
+                                       $wgSVGConverters[$wgSVGConverter]
+                               );
+
+                               $env = [];
+                               if ( $lang !== false ) {
+                                       $env['LANG'] = $lang;
+                               }
+
+                               wfDebug( __METHOD__ . ": $cmd\n" );
+                               $err = wfShellExecWithStderr( $cmd, $retval, $env );
+                       }
+               }
+               $removed = $this->removeBadFile( $dstPath, $retval );
+               if ( $retval != 0 || $removed ) {
+                       $this->logErrorForExternalProcess( $retval, $err, $cmd );
+                       return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
+               }
+
+               return true;
+       }
+
+       public static function rasterizeImagickExt( $srcPath, $dstPath, $width, $height ) {
+               $im = new Imagick( $srcPath );
+               $im->setImageFormat( 'png' );
+               $im->setBackgroundColor( 'transparent' );
+               $im->setImageDepth( 8 );
+
+               if ( !$im->thumbnailImage( intval( $width ), intval( $height ), /* fit */ false ) ) {
+                       return 'Could not resize image';
+               }
+               if ( !$im->writeImage( $dstPath ) ) {
+                       return "Could not write to $dstPath";
+               }
+       }
+
+       /**
+        * @param File|FSFile $file
+        * @param string $path Unused
+        * @param bool|array $metadata
+        * @return array
+        */
+       function getImageSize( $file, $path, $metadata = false ) {
+               if ( $metadata === false && $file instanceof File ) {
+                       $metadata = $file->getMetadata();
+               }
+               $metadata = $this->unpackMetadata( $metadata );
+
+               if ( isset( $metadata['width'] ) && isset( $metadata['height'] ) ) {
+                       return [ $metadata['width'], $metadata['height'], 'SVG',
+                               "width=\"{$metadata['width']}\" height=\"{$metadata['height']}\"" ];
+               } else { // error
+                       return [ 0, 0, 'SVG', "width=\"0\" height=\"0\"" ];
+               }
+       }
+
+       function getThumbType( $ext, $mime, $params = null ) {
+               return [ 'png', 'image/png' ];
+       }
+
+       /**
+        * Subtitle for the image. Different from the base
+        * class so it can be denoted that SVG's have
+        * a "nominal" resolution, and not a fixed one,
+        * as well as so animation can be denoted.
+        *
+        * @param File $file
+        * @return string
+        */
+       function getLongDesc( $file ) {
+               global $wgLang;
+
+               $metadata = $this->unpackMetadata( $file->getMetadata() );
+               if ( isset( $metadata['error'] ) ) {
+                       return wfMessage( 'svg-long-error', $metadata['error']['message'] )->text();
+               }
+
+               $size = $wgLang->formatSize( $file->getSize() );
+
+               if ( $this->isAnimatedImage( $file ) ) {
+                       $msg = wfMessage( 'svg-long-desc-animated' );
+               } else {
+                       $msg = wfMessage( 'svg-long-desc' );
+               }
+
+               $msg->numParams( $file->getWidth(), $file->getHeight() )->params( $size );
+
+               return $msg->parse();
+       }
+
+       /**
+        * @param File|FSFile $file
+        * @param string $filename
+        * @return string Serialised metadata
+        */
+       function getMetadata( $file, $filename ) {
+               $metadata = [ 'version' => self::SVG_METADATA_VERSION ];
+               try {
+                       $metadata += SVGMetadataExtractor::getMetadata( $filename );
+               } catch ( Exception $e ) { // @todo SVG specific exceptions
+                       // File not found, broken, etc.
+                       $metadata['error'] = [
+                               'message' => $e->getMessage(),
+                               'code' => $e->getCode()
+                       ];
+                       wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+               }
+
+               return serialize( $metadata );
+       }
+
+       function unpackMetadata( $metadata ) {
+               Wikimedia\suppressWarnings();
+               $unser = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+               if ( isset( $unser['version'] ) && $unser['version'] == self::SVG_METADATA_VERSION ) {
+                       return $unser;
+               } else {
+                       return false;
+               }
+       }
+
+       function getMetadataType( $image ) {
+               return 'parsed-svg';
+       }
+
+       function isMetadataValid( $image, $metadata ) {
+               $meta = $this->unpackMetadata( $metadata );
+               if ( $meta === false ) {
+                       return self::METADATA_BAD;
+               }
+               if ( !isset( $meta['originalWidth'] ) ) {
+                       // Old but compatible
+                       return self::METADATA_COMPATIBLE;
+               }
+
+               return self::METADATA_GOOD;
+       }
+
+       protected function visibleMetadataFields() {
+               $fields = [ 'objectname', 'imagedescription' ];
+
+               return $fields;
+       }
+
+       /**
+        * @param File $file
+        * @param bool|IContextSource $context Context to use (optional)
+        * @return array|bool
+        */
+       function formatMetadata( $file, $context = false ) {
+               $result = [
+                       'visible' => [],
+                       'collapsed' => []
+               ];
+               $metadata = $file->getMetadata();
+               if ( !$metadata ) {
+                       return false;
+               }
+               $metadata = $this->unpackMetadata( $metadata );
+               if ( !$metadata || isset( $metadata['error'] ) ) {
+                       return false;
+               }
+
+               /* @todo Add a formatter
+               $format = new FormatSVG( $metadata );
+               $formatted = $format->getFormattedData();
+               */
+
+               // Sort fields into visible and collapsed
+               $visibleFields = $this->visibleMetadataFields();
+
+               $showMeta = false;
+               foreach ( $metadata as $name => $value ) {
+                       $tag = strtolower( $name );
+                       if ( isset( self::$metaConversion[$tag] ) ) {
+                               $tag = strtolower( self::$metaConversion[$tag] );
+                       } else {
+                               // Do not output other metadata not in list
+                               continue;
+                       }
+                       $showMeta = true;
+                       self::addMeta( $result,
+                               in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
+                               'exif',
+                               $tag,
+                               $value
+                       );
+               }
+
+               return $showMeta ? $result : false;
+       }
+
+       /**
+        * @param string $name Parameter name
+        * @param mixed $value Parameter value
+        * @return bool Validity
+        */
+       public function validateParam( $name, $value ) {
+               if ( in_array( $name, [ 'width', 'height' ] ) ) {
+                       // Reject negative heights, widths
+                       return ( $value > 0 );
+               } elseif ( $name == 'lang' ) {
+                       // Validate $code
+                       if ( $value === '' || !Language::isValidCode( $value ) ) {
+                               return false;
+                       }
+
+                       return true;
+               }
+
+               // Only lang, width and height are acceptable keys
+               return false;
+       }
+
+       /**
+        * @param array $params Name=>value pairs of parameters
+        * @return string Filename to use
+        */
+       public function makeParamString( $params ) {
+               $lang = '';
+               if ( isset( $params['lang'] ) && $params['lang'] !== 'en' ) {
+                       $lang = 'lang' . strtolower( $params['lang'] ) . '-';
+               }
+               if ( !isset( $params['width'] ) ) {
+                       return false;
+               }
+
+               return "$lang{$params['width']}px";
+       }
+
+       public function parseParamString( $str ) {
+               $m = false;
+               if ( preg_match( '/^lang([a-z]+(?:-[a-z]+)*)-(\d+)px$/i', $str, $m ) ) {
+                       return [ 'width' => array_pop( $m ), 'lang' => $m[1] ];
+               } elseif ( preg_match( '/^(\d+)px$/', $str, $m ) ) {
+                       return [ 'width' => $m[1], 'lang' => 'en' ];
+               } else {
+                       return false;
+               }
+       }
+
+       public function getParamMap() {
+               return [ 'img_lang' => 'lang', 'img_width' => 'width' ];
+       }
+
+       /**
+        * @param array $params
+        * @return array
+        */
+       function getScriptParams( $params ) {
+               $scriptParams = [ 'width' => $params['width'] ];
+               if ( isset( $params['lang'] ) ) {
+                       $scriptParams['lang'] = $params['lang'];
+               }
+
+               return $scriptParams;
+       }
+
+       public function getCommonMetaArray( File $file ) {
+               $metadata = $file->getMetadata();
+               if ( !$metadata ) {
+                       return [];
+               }
+               $metadata = $this->unpackMetadata( $metadata );
+               if ( !$metadata || isset( $metadata['error'] ) ) {
+                       return [];
+               }
+               $stdMetadata = [];
+               foreach ( $metadata as $name => $value ) {
+                       $tag = strtolower( $name );
+                       if ( $tag === 'originalwidth' || $tag === 'originalheight' ) {
+                               // Skip these. In the exif metadata stuff, it is assumed these
+                               // are measured in px, which is not the case here.
+                               continue;
+                       }
+                       if ( isset( self::$metaConversion[$tag] ) ) {
+                               $tag = self::$metaConversion[$tag];
+                               $stdMetadata[$tag] = $value;
+                       }
+               }
+
+               return $stdMetadata;
+       }
+}
diff --git a/includes/media/Tiff.php b/includes/media/Tiff.php
deleted file mode 100644 (file)
index f0f4cda..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-/**
- * Handler for Tiff images.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for Tiff images.
- *
- * @ingroup Media
- */
-class TiffHandler extends ExifBitmapHandler {
-       const EXPENSIVE_SIZE_LIMIT = 10485760; // TIFF files over 10M are considered expensive to thumbnail
-
-       /**
-        * Conversion to PNG for inline display can be disabled here...
-        * Note scaling should work with ImageMagick, but may not with GD scaling.
-        *
-        * Files pulled from an another MediaWiki instance via ForeignAPIRepo /
-        * InstantCommons will have thumbnails managed from the remote instance,
-        * so we can skip this check.
-        *
-        * @param File $file
-        * @return bool
-        */
-       public function canRender( $file ) {
-               global $wgTiffThumbnailType;
-
-               return (bool)$wgTiffThumbnailType
-                       || $file->getRepo() instanceof ForeignAPIRepo;
-       }
-
-       /**
-        * Browsers don't support TIFF inline generally...
-        * For inline display, we need to convert to PNG.
-        *
-        * @param File $file
-        * @return bool
-        */
-       public function mustRender( $file ) {
-               return true;
-       }
-
-       /**
-        * @param string $ext
-        * @param string $mime
-        * @param array $params
-        * @return bool
-        */
-       function getThumbType( $ext, $mime, $params = null ) {
-               global $wgTiffThumbnailType;
-
-               return $wgTiffThumbnailType;
-       }
-
-       /**
-        * @param File|FSFile $image
-        * @param string $filename
-        * @throws MWException
-        * @return string
-        */
-       function getMetadata( $image, $filename ) {
-               global $wgShowEXIF;
-
-               if ( $wgShowEXIF ) {
-                       try {
-                               $meta = BitmapMetadataHandler::Tiff( $filename );
-                               if ( !is_array( $meta ) ) {
-                                       // This should never happen, but doesn't hurt to be paranoid.
-                                       throw new MWException( 'Metadata array is not an array' );
-                               }
-                               $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
-
-                               return serialize( $meta );
-                       } catch ( Exception $e ) {
-                               // BitmapMetadataHandler throws an exception in certain exceptional
-                               // cases like if file does not exist.
-                               wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
-
-                               return ExifBitmapHandler::BROKEN_FILE;
-                       }
-               } else {
-                       return '';
-               }
-       }
-
-       public function isExpensiveToThumbnail( $file ) {
-               return $file->getSize() > static::EXPENSIVE_SIZE_LIMIT;
-       }
-}
diff --git a/includes/media/TiffHandler.php b/includes/media/TiffHandler.php
new file mode 100644 (file)
index 0000000..f0f4cda
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Handler for Tiff images.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for Tiff images.
+ *
+ * @ingroup Media
+ */
+class TiffHandler extends ExifBitmapHandler {
+       const EXPENSIVE_SIZE_LIMIT = 10485760; // TIFF files over 10M are considered expensive to thumbnail
+
+       /**
+        * Conversion to PNG for inline display can be disabled here...
+        * Note scaling should work with ImageMagick, but may not with GD scaling.
+        *
+        * Files pulled from an another MediaWiki instance via ForeignAPIRepo /
+        * InstantCommons will have thumbnails managed from the remote instance,
+        * so we can skip this check.
+        *
+        * @param File $file
+        * @return bool
+        */
+       public function canRender( $file ) {
+               global $wgTiffThumbnailType;
+
+               return (bool)$wgTiffThumbnailType
+                       || $file->getRepo() instanceof ForeignAPIRepo;
+       }
+
+       /**
+        * Browsers don't support TIFF inline generally...
+        * For inline display, we need to convert to PNG.
+        *
+        * @param File $file
+        * @return bool
+        */
+       public function mustRender( $file ) {
+               return true;
+       }
+
+       /**
+        * @param string $ext
+        * @param string $mime
+        * @param array $params
+        * @return bool
+        */
+       function getThumbType( $ext, $mime, $params = null ) {
+               global $wgTiffThumbnailType;
+
+               return $wgTiffThumbnailType;
+       }
+
+       /**
+        * @param File|FSFile $image
+        * @param string $filename
+        * @throws MWException
+        * @return string
+        */
+       function getMetadata( $image, $filename ) {
+               global $wgShowEXIF;
+
+               if ( $wgShowEXIF ) {
+                       try {
+                               $meta = BitmapMetadataHandler::Tiff( $filename );
+                               if ( !is_array( $meta ) ) {
+                                       // This should never happen, but doesn't hurt to be paranoid.
+                                       throw new MWException( 'Metadata array is not an array' );
+                               }
+                               $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+
+                               return serialize( $meta );
+                       } catch ( Exception $e ) {
+                               // BitmapMetadataHandler throws an exception in certain exceptional
+                               // cases like if file does not exist.
+                               wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
+                               return ExifBitmapHandler::BROKEN_FILE;
+                       }
+               } else {
+                       return '';
+               }
+       }
+
+       public function isExpensiveToThumbnail( $file ) {
+               return $file->getSize() > static::EXPENSIVE_SIZE_LIMIT;
+       }
+}
diff --git a/includes/media/WebP.php b/includes/media/WebP.php
deleted file mode 100644 (file)
index 295a978..0000000
+++ /dev/null
@@ -1,309 +0,0 @@
-<?php
-/**
- * Handler for Google's WebP format <https://developers.google.com/speed/webp/>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-/**
- * Handler for Google's WebP format <https://developers.google.com/speed/webp/>
- *
- * @ingroup Media
- */
-class WebPHandler extends BitmapHandler {
-       const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
-       /**
-        * @var int Minimum chunk header size to be able to read all header types
-        */
-       const MINIMUM_CHUNK_HEADER_LENGTH = 18;
-       /**
-        * @var int version of the metadata stored in db records
-        */
-       const _MW_WEBP_VERSION = 1;
-
-       const VP8X_ICC = 32;
-       const VP8X_ALPHA = 16;
-       const VP8X_EXIF = 8;
-       const VP8X_XMP = 4;
-       const VP8X_ANIM = 2;
-
-       public function getMetadata( $image, $filename ) {
-               $parsedWebPData = self::extractMetadata( $filename );
-               if ( !$parsedWebPData ) {
-                       return self::BROKEN_FILE;
-               }
-
-               $parsedWebPData['metadata']['_MW_WEBP_VERSION'] = self::_MW_WEBP_VERSION;
-               return serialize( $parsedWebPData );
-       }
-
-       public function getMetadataType( $image ) {
-               return 'parsed-webp';
-       }
-
-       public function isMetadataValid( $image, $metadata ) {
-               if ( $metadata === self::BROKEN_FILE ) {
-                               // Do not repetitivly regenerate metadata on broken file.
-                               return self::METADATA_GOOD;
-               }
-
-               Wikimedia\suppressWarnings();
-               $data = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-
-               if ( !$data || !is_array( $data ) ) {
-                               wfDebug( __METHOD__ . " invalid WebP metadata\n" );
-
-                               return self::METADATA_BAD;
-               }
-
-               if ( !isset( $data['metadata']['_MW_WEBP_VERSION'] )
-                               || $data['metadata']['_MW_WEBP_VERSION'] != self::_MW_WEBP_VERSION
-               ) {
-                               wfDebug( __METHOD__ . " old but compatible WebP metadata\n" );
-
-                               return self::METADATA_COMPATIBLE;
-               }
-               return self::METADATA_GOOD;
-       }
-
-       /**
-        * Extracts the image size and WebP type from a file
-        *
-        * @param string $filename
-        * @return array|bool Header data array with entries 'compression', 'width' and 'height',
-        * where 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'. False if
-        * file is not a valid WebP file.
-        */
-       public static function extractMetadata( $filename ) {
-               wfDebugLog( 'WebP', __METHOD__ . ": Extracting metadata from $filename\n" );
-
-               $info = RiffExtractor::findChunksFromFile( $filename, 100 );
-               if ( $info === false ) {
-                       wfDebugLog( 'WebP', __METHOD__ . ": Not a valid RIFF file\n" );
-                       return false;
-               }
-
-               if ( $info['fourCC'] != 'WEBP' ) {
-                       wfDebugLog( 'WebP', __METHOD__ . ': FourCC was not WEBP: ' .
-                               bin2hex( $info['fourCC'] ) . " \n" );
-                       return false;
-               }
-
-               $metadata = self::extractMetadataFromChunks( $info['chunks'], $filename );
-               if ( !$metadata ) {
-                       wfDebugLog( 'WebP', __METHOD__ . ": No VP8 chunks found\n" );
-                       return false;
-               }
-
-               return $metadata;
-       }
-
-       /**
-        * Extracts the image size and WebP type from a file based on the chunk list
-        * @param array $chunks Chunks as extracted by RiffExtractor
-        * @param string $filename
-        * @return array Header data array with entries 'compression', 'width' and 'height', where
-        * 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'
-        */
-       public static function extractMetadataFromChunks( $chunks, $filename ) {
-               $vp8Info = [];
-
-               foreach ( $chunks as $chunk ) {
-                       if ( !in_array( $chunk['fourCC'], [ 'VP8 ', 'VP8L', 'VP8X' ] ) ) {
-                               // Not a chunk containing interesting metadata
-                               continue;
-                       }
-
-                       $chunkHeader = file_get_contents( $filename, false, null,
-                               $chunk['start'], self::MINIMUM_CHUNK_HEADER_LENGTH );
-                       wfDebugLog( 'WebP', __METHOD__ . ": {$chunk['fourCC']}\n" );
-
-                       switch ( $chunk['fourCC'] ) {
-                               case 'VP8 ':
-                                       return array_merge( $vp8Info,
-                                               self::decodeLossyChunkHeader( $chunkHeader ) );
-                               case 'VP8L':
-                                       return array_merge( $vp8Info,
-                                               self::decodeLosslessChunkHeader( $chunkHeader ) );
-                               case 'VP8X':
-                                       $vp8Info = array_merge( $vp8Info,
-                                               self::decodeExtendedChunkHeader( $chunkHeader ) );
-                                       // Continue looking for other chunks to improve the metadata
-                                       break;
-                       }
-               }
-               return $vp8Info;
-       }
-
-       /**
-        * Decodes a lossy chunk header
-        * @param string $header First few bytes of the header, expected to be at least 18 bytes long
-        * @return bool|array See WebPHandler::decodeHeader
-        */
-       protected static function decodeLossyChunkHeader( $header ) {
-               // Bytes 0-3 are 'VP8 '
-               // Bytes 4-7 are the VP8 stream size
-               // Bytes 8-10 are the frame tag
-               // Bytes 11-13 are 0x9D 0x01 0x2A called the sync code
-               $syncCode = substr( $header, 11, 3 );
-               if ( $syncCode != "\x9D\x01\x2A" ) {
-                       wfDebugLog( 'WebP', __METHOD__ . ': Invalid sync code: ' .
-                               bin2hex( $syncCode ) . "\n" );
-                       return [];
-               }
-               // Bytes 14-17 are image size
-               $imageSize = unpack( 'v2', substr( $header, 14, 4 ) );
-               // Image sizes are 14 bit, 2 MSB are scaling parameters which are ignored here
-               return [
-                       'compression' => 'lossy',
-                       'width' => $imageSize[1] & 0x3FFF,
-                       'height' => $imageSize[2] & 0x3FFF
-               ];
-       }
-
-       /**
-        * Decodes a lossless chunk header
-        * @param string $header First few bytes of the header, expected to be at least 13 bytes long
-        * @return bool|array See WebPHandler::decodeHeader
-        */
-       public static function decodeLosslessChunkHeader( $header ) {
-               // Bytes 0-3 are 'VP8L'
-               // Bytes 4-7 are chunk stream size
-               // Byte 8 is 0x2F called the signature
-               if ( $header{8} != "\x2F" ) {
-                       wfDebugLog( 'WebP', __METHOD__ . ': Invalid signature: ' .
-                               bin2hex( $header{8} ) . "\n" );
-                       return [];
-               }
-               // Bytes 9-12 contain the image size
-               // Bits 0-13 are width-1; bits 15-27 are height-1
-               $imageSize = unpack( 'C4', substr( $header, 9, 4 ) );
-               return [
-                               'compression' => 'lossless',
-                               'width' => ( $imageSize[1] | ( ( $imageSize[2] & 0x3F ) << 8 ) ) + 1,
-                               'height' => ( ( ( $imageSize[2] & 0xC0 ) >> 6 ) |
-                                               ( $imageSize[3] << 2 ) | ( ( $imageSize[4] & 0x03 ) << 10 ) ) + 1
-               ];
-       }
-
-       /**
-        * Decodes an extended chunk header
-        * @param string $header First few bytes of the header, expected to be at least 18 bytes long
-        * @return bool|array See WebPHandler::decodeHeader
-        */
-       public static function decodeExtendedChunkHeader( $header ) {
-               // Bytes 0-3 are 'VP8X'
-               // Byte 4-7 are chunk length
-               // Byte 8-11 are a flag bytes
-               $flags = unpack( 'c', substr( $header, 8, 1 ) );
-
-               // Byte 12-17 are image size (24 bits)
-               $width = unpack( 'V', substr( $header, 12, 3 ) . "\x00" );
-               $height = unpack( 'V', substr( $header, 15, 3 ) . "\x00" );
-
-               return [
-                       'compression' => 'unknown',
-                       'animated' => ( $flags[1] & self::VP8X_ANIM ) == self::VP8X_ANIM,
-                       'transparency' => ( $flags[1] & self::VP8X_ALPHA ) == self::VP8X_ALPHA,
-                       'width' => ( $width[1] & 0xFFFFFF ) + 1,
-                       'height' => ( $height[1] & 0xFFFFFF ) + 1
-               ];
-       }
-
-       public function getImageSize( $file, $path, $metadata = false ) {
-               if ( $file === null ) {
-                       $metadata = self::getMetadata( $file, $path );
-               }
-               if ( $metadata === false && $file instanceof File ) {
-                       $metadata = $file->getMetadata();
-               }
-
-               Wikimedia\suppressWarnings();
-               $metadata = unserialize( $metadata );
-               Wikimedia\restoreWarnings();
-
-               if ( $metadata == false ) {
-                       return false;
-               }
-               return [ $metadata['width'], $metadata['height'] ];
-       }
-
-       /**
-        * @param File $file
-        * @return bool True, not all browsers support WebP
-        */
-       public function mustRender( $file ) {
-               return true;
-       }
-
-       /**
-        * @param File $file
-        * @return bool False if we are unable to render this image
-        */
-       public function canRender( $file ) {
-               if ( self::isAnimatedImage( $file ) ) {
-                       return false;
-               }
-               return true;
-       }
-
-       /**
-        * @param File $image
-        * @return bool
-        */
-       public function isAnimatedImage( $image ) {
-               $ser = $image->getMetadata();
-               if ( $ser ) {
-                       $metadata = unserialize( $ser );
-                       if ( isset( $metadata['animated'] ) && $metadata['animated'] === true ) {
-                               return true;
-                       }
-               }
-
-               return false;
-       }
-
-       public function canAnimateThumbnail( $file ) {
-               return false;
-       }
-
-       /**
-        * Render files as PNG
-        *
-        * @param string $ext
-        * @param string $mime
-        * @param array|null $params
-        * @return array
-        */
-       public function getThumbType( $ext, $mime, $params = null ) {
-               return [ 'png', 'image/png' ];
-       }
-
-       /**
-        * Must use "im" for XCF
-        *
-        * @param string $dstPath
-        * @param bool $checkDstPath
-        * @return string
-        */
-       protected function getScalerType( $dstPath, $checkDstPath = true ) {
-               return 'im';
-       }
-}
diff --git a/includes/media/WebPHandler.php b/includes/media/WebPHandler.php
new file mode 100644 (file)
index 0000000..295a978
--- /dev/null
@@ -0,0 +1,309 @@
+<?php
+/**
+ * Handler for Google's WebP format <https://developers.google.com/speed/webp/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for Google's WebP format <https://developers.google.com/speed/webp/>
+ *
+ * @ingroup Media
+ */
+class WebPHandler extends BitmapHandler {
+       const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
+       /**
+        * @var int Minimum chunk header size to be able to read all header types
+        */
+       const MINIMUM_CHUNK_HEADER_LENGTH = 18;
+       /**
+        * @var int version of the metadata stored in db records
+        */
+       const _MW_WEBP_VERSION = 1;
+
+       const VP8X_ICC = 32;
+       const VP8X_ALPHA = 16;
+       const VP8X_EXIF = 8;
+       const VP8X_XMP = 4;
+       const VP8X_ANIM = 2;
+
+       public function getMetadata( $image, $filename ) {
+               $parsedWebPData = self::extractMetadata( $filename );
+               if ( !$parsedWebPData ) {
+                       return self::BROKEN_FILE;
+               }
+
+               $parsedWebPData['metadata']['_MW_WEBP_VERSION'] = self::_MW_WEBP_VERSION;
+               return serialize( $parsedWebPData );
+       }
+
+       public function getMetadataType( $image ) {
+               return 'parsed-webp';
+       }
+
+       public function isMetadataValid( $image, $metadata ) {
+               if ( $metadata === self::BROKEN_FILE ) {
+                               // Do not repetitivly regenerate metadata on broken file.
+                               return self::METADATA_GOOD;
+               }
+
+               Wikimedia\suppressWarnings();
+               $data = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+
+               if ( !$data || !is_array( $data ) ) {
+                               wfDebug( __METHOD__ . " invalid WebP metadata\n" );
+
+                               return self::METADATA_BAD;
+               }
+
+               if ( !isset( $data['metadata']['_MW_WEBP_VERSION'] )
+                               || $data['metadata']['_MW_WEBP_VERSION'] != self::_MW_WEBP_VERSION
+               ) {
+                               wfDebug( __METHOD__ . " old but compatible WebP metadata\n" );
+
+                               return self::METADATA_COMPATIBLE;
+               }
+               return self::METADATA_GOOD;
+       }
+
+       /**
+        * Extracts the image size and WebP type from a file
+        *
+        * @param string $filename
+        * @return array|bool Header data array with entries 'compression', 'width' and 'height',
+        * where 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'. False if
+        * file is not a valid WebP file.
+        */
+       public static function extractMetadata( $filename ) {
+               wfDebugLog( 'WebP', __METHOD__ . ": Extracting metadata from $filename\n" );
+
+               $info = RiffExtractor::findChunksFromFile( $filename, 100 );
+               if ( $info === false ) {
+                       wfDebugLog( 'WebP', __METHOD__ . ": Not a valid RIFF file\n" );
+                       return false;
+               }
+
+               if ( $info['fourCC'] != 'WEBP' ) {
+                       wfDebugLog( 'WebP', __METHOD__ . ': FourCC was not WEBP: ' .
+                               bin2hex( $info['fourCC'] ) . " \n" );
+                       return false;
+               }
+
+               $metadata = self::extractMetadataFromChunks( $info['chunks'], $filename );
+               if ( !$metadata ) {
+                       wfDebugLog( 'WebP', __METHOD__ . ": No VP8 chunks found\n" );
+                       return false;
+               }
+
+               return $metadata;
+       }
+
+       /**
+        * Extracts the image size and WebP type from a file based on the chunk list
+        * @param array $chunks Chunks as extracted by RiffExtractor
+        * @param string $filename
+        * @return array Header data array with entries 'compression', 'width' and 'height', where
+        * 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'
+        */
+       public static function extractMetadataFromChunks( $chunks, $filename ) {
+               $vp8Info = [];
+
+               foreach ( $chunks as $chunk ) {
+                       if ( !in_array( $chunk['fourCC'], [ 'VP8 ', 'VP8L', 'VP8X' ] ) ) {
+                               // Not a chunk containing interesting metadata
+                               continue;
+                       }
+
+                       $chunkHeader = file_get_contents( $filename, false, null,
+                               $chunk['start'], self::MINIMUM_CHUNK_HEADER_LENGTH );
+                       wfDebugLog( 'WebP', __METHOD__ . ": {$chunk['fourCC']}\n" );
+
+                       switch ( $chunk['fourCC'] ) {
+                               case 'VP8 ':
+                                       return array_merge( $vp8Info,
+                                               self::decodeLossyChunkHeader( $chunkHeader ) );
+                               case 'VP8L':
+                                       return array_merge( $vp8Info,
+                                               self::decodeLosslessChunkHeader( $chunkHeader ) );
+                               case 'VP8X':
+                                       $vp8Info = array_merge( $vp8Info,
+                                               self::decodeExtendedChunkHeader( $chunkHeader ) );
+                                       // Continue looking for other chunks to improve the metadata
+                                       break;
+                       }
+               }
+               return $vp8Info;
+       }
+
+       /**
+        * Decodes a lossy chunk header
+        * @param string $header First few bytes of the header, expected to be at least 18 bytes long
+        * @return bool|array See WebPHandler::decodeHeader
+        */
+       protected static function decodeLossyChunkHeader( $header ) {
+               // Bytes 0-3 are 'VP8 '
+               // Bytes 4-7 are the VP8 stream size
+               // Bytes 8-10 are the frame tag
+               // Bytes 11-13 are 0x9D 0x01 0x2A called the sync code
+               $syncCode = substr( $header, 11, 3 );
+               if ( $syncCode != "\x9D\x01\x2A" ) {
+                       wfDebugLog( 'WebP', __METHOD__ . ': Invalid sync code: ' .
+                               bin2hex( $syncCode ) . "\n" );
+                       return [];
+               }
+               // Bytes 14-17 are image size
+               $imageSize = unpack( 'v2', substr( $header, 14, 4 ) );
+               // Image sizes are 14 bit, 2 MSB are scaling parameters which are ignored here
+               return [
+                       'compression' => 'lossy',
+                       'width' => $imageSize[1] & 0x3FFF,
+                       'height' => $imageSize[2] & 0x3FFF
+               ];
+       }
+
+       /**
+        * Decodes a lossless chunk header
+        * @param string $header First few bytes of the header, expected to be at least 13 bytes long
+        * @return bool|array See WebPHandler::decodeHeader
+        */
+       public static function decodeLosslessChunkHeader( $header ) {
+               // Bytes 0-3 are 'VP8L'
+               // Bytes 4-7 are chunk stream size
+               // Byte 8 is 0x2F called the signature
+               if ( $header{8} != "\x2F" ) {
+                       wfDebugLog( 'WebP', __METHOD__ . ': Invalid signature: ' .
+                               bin2hex( $header{8} ) . "\n" );
+                       return [];
+               }
+               // Bytes 9-12 contain the image size
+               // Bits 0-13 are width-1; bits 15-27 are height-1
+               $imageSize = unpack( 'C4', substr( $header, 9, 4 ) );
+               return [
+                               'compression' => 'lossless',
+                               'width' => ( $imageSize[1] | ( ( $imageSize[2] & 0x3F ) << 8 ) ) + 1,
+                               'height' => ( ( ( $imageSize[2] & 0xC0 ) >> 6 ) |
+                                               ( $imageSize[3] << 2 ) | ( ( $imageSize[4] & 0x03 ) << 10 ) ) + 1
+               ];
+       }
+
+       /**
+        * Decodes an extended chunk header
+        * @param string $header First few bytes of the header, expected to be at least 18 bytes long
+        * @return bool|array See WebPHandler::decodeHeader
+        */
+       public static function decodeExtendedChunkHeader( $header ) {
+               // Bytes 0-3 are 'VP8X'
+               // Byte 4-7 are chunk length
+               // Byte 8-11 are a flag bytes
+               $flags = unpack( 'c', substr( $header, 8, 1 ) );
+
+               // Byte 12-17 are image size (24 bits)
+               $width = unpack( 'V', substr( $header, 12, 3 ) . "\x00" );
+               $height = unpack( 'V', substr( $header, 15, 3 ) . "\x00" );
+
+               return [
+                       'compression' => 'unknown',
+                       'animated' => ( $flags[1] & self::VP8X_ANIM ) == self::VP8X_ANIM,
+                       'transparency' => ( $flags[1] & self::VP8X_ALPHA ) == self::VP8X_ALPHA,
+                       'width' => ( $width[1] & 0xFFFFFF ) + 1,
+                       'height' => ( $height[1] & 0xFFFFFF ) + 1
+               ];
+       }
+
+       public function getImageSize( $file, $path, $metadata = false ) {
+               if ( $file === null ) {
+                       $metadata = self::getMetadata( $file, $path );
+               }
+               if ( $metadata === false && $file instanceof File ) {
+                       $metadata = $file->getMetadata();
+               }
+
+               Wikimedia\suppressWarnings();
+               $metadata = unserialize( $metadata );
+               Wikimedia\restoreWarnings();
+
+               if ( $metadata == false ) {
+                       return false;
+               }
+               return [ $metadata['width'], $metadata['height'] ];
+       }
+
+       /**
+        * @param File $file
+        * @return bool True, not all browsers support WebP
+        */
+       public function mustRender( $file ) {
+               return true;
+       }
+
+       /**
+        * @param File $file
+        * @return bool False if we are unable to render this image
+        */
+       public function canRender( $file ) {
+               if ( self::isAnimatedImage( $file ) ) {
+                       return false;
+               }
+               return true;
+       }
+
+       /**
+        * @param File $image
+        * @return bool
+        */
+       public function isAnimatedImage( $image ) {
+               $ser = $image->getMetadata();
+               if ( $ser ) {
+                       $metadata = unserialize( $ser );
+                       if ( isset( $metadata['animated'] ) && $metadata['animated'] === true ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       public function canAnimateThumbnail( $file ) {
+               return false;
+       }
+
+       /**
+        * Render files as PNG
+        *
+        * @param string $ext
+        * @param string $mime
+        * @param array|null $params
+        * @return array
+        */
+       public function getThumbType( $ext, $mime, $params = null ) {
+               return [ 'png', 'image/png' ];
+       }
+
+       /**
+        * Must use "im" for XCF
+        *
+        * @param string $dstPath
+        * @param bool $checkDstPath
+        * @return string
+        */
+       protected function getScalerType( $dstPath, $checkDstPath = true ) {
+               return 'im';
+       }
+}
index 1173dd2..c366903 100644 (file)
@@ -291,19 +291,34 @@ class BlockLevelPass {
                        if ( 0 == $prefixLength ) {
                                # No prefix (not in list)--go to paragraph mode
                                # @todo consider using a stack for nestable elements like span, table and div
+
+                               // P-wrapping and indent-pre are suppressed inside, not outside
+                               $blockElems = 'table|h1|h2|h3|h4|h5|h6|pre|p|ul|ol|dl|li';
+                               // P-wrapping and indent-pre are suppressed outside, not inside
+                               $antiBlockElems = 'td|th';
+
                                $openMatch = preg_match(
-                                       '/(?:<table|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|'
-                                               . '<p|<ul|<ol|<dl|<li|<\\/tr|<\\/td|<\\/th)\\b/iS',
+                                       '/<('
+                                               . "({$blockElems})|\\/({$antiBlockElems})|"
+                                               // Always suppresses
+                                               . '\\/?(tr)'
+                                               . ')\\b/iS',
                                        $t
                                );
                                $closeMatch = preg_match(
-                                       '/(?:<\\/table|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'
-                                               . '<td|<th|<\\/?blockquote|<\\/?div|<hr|<\\/pre|<\\/p|<\\/mw:|'
-                                               . Parser::MARKER_PREFIX
-                                               . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)\\b/iS',
+                                       '/<('
+                                               . "\\/({$blockElems})|({$antiBlockElems})|"
+                                               // Never suppresses
+                                               . '\\/?(center|blockquote|div|hr|mw:)'
+                                               . ')\\b/iS',
                                        $t
                                );
 
+                               // Any match closes the paragraph, but only when `!$closeMatch`
+                               // do we enter block mode.  The oddities with table rows and
+                               // cells are to avoid paragraph wrapping in interstitial spaces
+                               // leading to fostered content.
+
                                if ( $openMatch || $closeMatch ) {
                                        $pendingPTag = false;
                                        // Only close the paragraph if we're not inside a <pre> tag, or if
index 19cf573..5788986 100644 (file)
@@ -111,12 +111,6 @@ class MWTidy {
                        case 'RaggettExternal':
                                $instance = new MediaWiki\Tidy\RaggettExternal( $config );
                                break;
-                       case 'Html5Depurate':
-                               $instance = new MediaWiki\Tidy\Html5Depurate( $config );
-                               break;
-                       case 'Html5Internal':
-                               $instance = new MediaWiki\Tidy\Html5Internal( $config );
-                               break;
                        case 'RemexHtml':
                                $instance = new MediaWiki\Tidy\RemexDriver( $config );
                                break;
index f3a83db..aa015a6 100644 (file)
@@ -744,14 +744,24 @@ class ParserOutput extends CacheTime {
                }
        }
 
+       /**
+        * @see OutputPage::addModules
+        */
        public function addModules( $modules ) {
                $this->mModules = array_merge( $this->mModules, (array)$modules );
        }
 
+       /**
+        * @deprecated since 1.31 Use addModules() instead.
+        * @see OutputPage::addModuleScripts
+        */
        public function addModuleScripts( $modules ) {
                $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
        }
 
+       /**
+        * @see OutputPage::addModuleStyles
+        */
        public function addModuleStyles( $modules ) {
                $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
        }
index 64edbb2..104cd13 100644 (file)
@@ -566,6 +566,8 @@ class Preprocessor_DOM extends Preprocessor {
                        } elseif ( $found == 'line-end' ) {
                                $piece = $stack->top;
                                // A heading must be open, otherwise \n wouldn't have been in the search list
+                               // FIXME: Don't use assert()
+                               // phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.assert
                                assert( $piece->open === "\n" );
                                $part = $piece->getCurrentPart();
                                // Search back through the input to see if it has a proper close.
index c7f630d..8e74380 100644 (file)
@@ -504,6 +504,8 @@ class Preprocessor_Hash extends Preprocessor {
                        } elseif ( $found == 'line-end' ) {
                                $piece = $stack->top;
                                // A heading must be open, otherwise \n wouldn't have been in the search list
+                               // FIXME: Don't use assert()
+                               // phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.assert
                                assert( $piece->open === "\n" );
                                $part = $piece->getCurrentPart();
                                // Search back through the input to see if it has a proper close.
index b13e597..118442d 100644 (file)
@@ -1180,13 +1180,12 @@ class Sanitizer {
 
        /**
         * Given a value, escape it so that it can be used in an id attribute and
-        * return it.  This will use HTML5 validation if $wgExperimentalHtmlIds is
-        * true, allowing anything but ASCII whitespace.  Otherwise it will use
-        * HTML 4 rules, which means a narrow subset of ASCII, with bad characters
-        * escaped with lots of dots.
+        * return it.  This will use HTML5 validation, allowing anything but ASCII
+        * whitespace.
+        *
+        * To ensure we don't have to bother escaping anything, we also strip ', ".
+        * TODO: Is this the best tactic?
         *
-        * To ensure we don't have to bother escaping anything, we also strip ', ",
-        * & even if $wgExperimentalIds is true.  TODO: Is this the best tactic?
         * We also strip # because it upsets IE, and % because it could be
         * ambiguous if it's part of something that looks like a percent escape
         * (which don't work reliably in fragments cross-browser).
@@ -1204,28 +1203,12 @@ class Sanitizer {
         * @param string|array $options String or array of strings (default is array()):
         *   'noninitial': This is a non-initial fragment of an id, not a full id,
         *       so don't pay attention if the first character isn't valid at the
-        *       beginning of an id.  Only matters if $wgExperimentalHtmlIds is
-        *       false.
-        *   'legacy': Behave the way the old HTML 4-based ID escaping worked even
-        *       if $wgExperimentalHtmlIds is used, so we can generate extra
-        *       anchors and links won't break.
+        *       beginning of an id.
         * @return string
         */
        static function escapeId( $id, $options = [] ) {
-               global $wgExperimentalHtmlIds;
                $options = (array)$options;
 
-               if ( $wgExperimentalHtmlIds && !in_array( 'legacy', $options ) ) {
-                       $id = preg_replace( '/[ \t\n\r\f_\'"&#%]+/', '_', $id );
-                       $id = trim( $id, '_' );
-                       if ( $id === '' ) {
-                               // Must have been all whitespace to start with.
-                               return '_';
-                       } else {
-                               return $id;
-                       }
-               }
-
                // HTML4-style escaping
                static $replace = [
                        '%3A' => ':',
@@ -1337,14 +1320,6 @@ class Sanitizer {
                                $id = urlencode( str_replace( ' ', '_', $id ) );
                                $id = strtr( $id, $replace );
                                break;
-                       case 'html5-legacy':
-                               $id = preg_replace( '/[ \t\n\r\f_\'"&#%]+/', '_', $id );
-                               $id = trim( $id, '_' );
-                               if ( $id === '' ) {
-                                       // Must have been all whitespace to start with.
-                                       $id = '_';
-                               }
-                               break;
                        default:
                                throw new InvalidArgumentException( "Invalid mode '$mode' passed to '" . __METHOD__ );
                }
index 3bc21f7..2d7d73f 100644 (file)
@@ -42,13 +42,16 @@ use MessageLocalizer;
 use MWException;
 use MWNamespace;
 use MWTimestamp;
+use OutputPage;
 use Parser;
 use ParserOptions;
 use PreferencesForm;
+use PreferencesFormOOUI;
 use Psr\Log\LoggerAwareTrait;
 use Psr\Log\NullLogger;
 use Skin;
 use SpecialPage;
+use SpecialPreferences;
 use Status;
 use Title;
 use User;
@@ -127,6 +130,13 @@ class DefaultPreferencesFactory implements PreferencesFactory {
        public function getFormDescriptor( User $user, IContextSource $context ) {
                $preferences = [];
 
+               if ( SpecialPreferences::isOouiEnabled( $context ) ) {
+                       OutputPage::setupOOUI(
+                               strtolower( $context->getSkin()->getSkinName() ),
+                               $context->getLanguage()->getDir()
+                       );
+               }
+
                $canIPUseHTTPS = wfCanIPUseHTTPS( $context->getRequest()->getIP() );
                $this->profilePreferences( $user, $context, $preferences, $canIPUseHTTPS );
                $this->skinPreferences( $user, $context, $preferences );
@@ -254,6 +264,8 @@ class DefaultPreferencesFactory implements PreferencesFactory {
        protected function profilePreferences(
                User $user, IContextSource $context, &$defaultPreferences, $canIPUseHTTPS
        ) {
+               $oouiEnabled = SpecialPreferences::isOouiEnabled( $context );
+
                // retrieving user name for GENDER and misc.
                $userName = $user->getName();
 
@@ -365,13 +377,23 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
                        new PasswordAuthenticationRequest(), false )->isGood()
                ) {
-                       $link = $this->linkRenderer->makeLink( SpecialPage::getTitleFor( 'ChangePassword' ),
-                               $context->msg( 'prefs-resetpass' )->text(), [],
-                               [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
+                       if ( $oouiEnabled ) {
+                               $link = new \OOUI\ButtonWidget( [
+                                       'href' => SpecialPage::getTitleFor( 'ChangePassword' )->getLinkURL( [
+                                               'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
+                                       ] ),
+                                       'label' => $context->msg( 'prefs-resetpass' )->text(),
+                               ] );
+                       } else {
+                               $link = $this->linkRenderer->makeLink( SpecialPage::getTitleFor( 'ChangePassword' ),
+                                       $context->msg( 'prefs-resetpass' )->text(), [],
+                                       [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
+                       }
+
                        $defaultPreferences['password'] = [
                                'type' => 'info',
                                'raw' => true,
-                               'default' => $link,
+                               'default' => (string)$link,
                                'label-message' => 'yourpassword',
                                'section' => 'personal/info',
                        ];
@@ -519,16 +541,28 @@ class DefaultPreferencesFactory implements PreferencesFactory {
 
                                $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
                                if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'emailaddress' ) ) {
-                                       $link = $this->linkRenderer->makeLink(
-                                               SpecialPage::getTitleFor( 'ChangeEmail' ),
-                                               $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
-                                               [],
-                                               [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
-
-                                       $emailAddress .= $emailAddress == '' ? $link : (
-                                               $context->msg( 'word-separator' )->escaped()
-                                               . $context->msg( 'parentheses' )->rawParams( $link )->escaped()
-                                       );
+                                       if ( $oouiEnabled ) {
+                                               $link = new \OOUI\ButtonWidget( [
+                                                       'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
+                                                               'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
+                                                       ] ),
+                                                       'label' =>
+                                                               $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
+                                               ] );
+
+                                               $emailAddress .= $emailAddress == '' ? $link : ( '<br />' . $link );
+                                       } else {
+                                               $link = $this->linkRenderer->makeLink(
+                                                       SpecialPage::getTitleFor( 'ChangeEmail' ),
+                                                       $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
+                                                       [],
+                                                       [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
+
+                                               $emailAddress .= $emailAddress == '' ? $link : (
+                                                       $context->msg( 'word-separator' )->escaped()
+                                                       . $context->msg( 'parentheses' )->rawParams( $link )->escaped()
+                                               );
+                                       }
                                }
 
                                $defaultPreferences['emailaddress'] = [
@@ -562,11 +596,19 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                                                $emailauthenticationclass = 'mw-email-authenticated';
                                        } else {
                                                $disableEmailPrefs = true;
-                                               $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
-                                                       $this->linkRenderer->makeKnownLink(
-                                                               SpecialPage::getTitleFor( 'Confirmemail' ),
-                                                               $context->msg( 'emailconfirmlink' )->text()
-                                                       ) . '<br />';
+                                               if ( $oouiEnabled ) {
+                                                       $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
+                                                               new \OOUI\ButtonWidget( [
+                                                                       'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
+                                                                       'label' => $context->msg( 'emailconfirmlink' )->text(),
+                                                               ] );
+                                               } else {
+                                                       $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
+                                                               $this->linkRenderer->makeKnownLink(
+                                                                       SpecialPage::getTitleFor( 'Confirmemail' ),
+                                                                       $context->msg( 'emailconfirmlink' )->text()
+                                                               ) . '<br />';
+                                               }
                                                $emailauthenticationclass = "mw-email-not-authenticated";
                                        }
                                } else {
@@ -810,6 +852,7 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                        'default' => $tzSetting,
                        'size' => 20,
                        'section' => 'rendering/timeoffset',
+                       'id' => 'wpTimeCorrection',
                ];
        }
 
@@ -1048,28 +1091,44 @@ class DefaultPreferencesFactory implements PreferencesFactory {
        protected function watchlistPreferences(
                User $user, IContextSource $context, &$defaultPreferences
        ) {
+               $oouiEnabled = SpecialPreferences::isOouiEnabled( $context );
+
                $watchlistdaysMax = ceil( $this->config->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
 
                # # Watchlist #####################################
                if ( $user->isAllowed( 'editmywatchlist' ) ) {
-                       $editWatchlistLinks = [];
+                       $editWatchlistLinks = '';
+                       $editWatchlistLinksOld = [];
                        $editWatchlistModes = [
-                               'edit' => [ 'EditWatchlist', false ],
-                               'raw' => [ 'EditWatchlist', 'raw' ],
-                               'clear' => [ 'EditWatchlist', 'clear' ],
+                               'edit' => [ 'subpage' => false, 'flags' => [] ],
+                               'raw' => [ 'subpage' => 'raw', 'flags' => [] ],
+                               'clear' => [ 'subpage' => 'clear', 'flags' => [ 'destructive' ] ],
                        ];
-                       foreach ( $editWatchlistModes as $editWatchlistMode => $mode ) {
+                       foreach ( $editWatchlistModes as $mode => $options ) {
                                // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear
-                               $editWatchlistLinks[] = $this->linkRenderer->makeKnownLink(
-                                       SpecialPage::getTitleFor( $mode[0], $mode[1] ),
-                                       new HtmlArmor( $context->msg( "prefs-editwatchlist-{$editWatchlistMode}" )->parse() )
-                               );
+                               if ( $oouiEnabled ) {
+                                       $editWatchlistLinks .=
+                                               new \OOUI\ButtonWidget( [
+                                                       'href' => SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] )->getLinkURL(),
+                                                       'flags' => $options[ 'flags' ],
+                                                       'label' => new \OOUI\HtmlSnippet(
+                                                               $context->msg( "prefs-editwatchlist-{$mode}" )->parse()
+                                                       ),
+                                               ] );
+                               } else {
+                                       $editWatchlistLinksOld[] = $this->linkRenderer->makeKnownLink(
+                                               SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] ),
+                                               new HtmlArmor( $context->msg( "prefs-editwatchlist-{$mode}" )->parse() )
+                                       );
+                               }
                        }
 
                        $defaultPreferences['editwatchlist'] = [
                                'type' => 'info',
                                'raw' => true,
-                               'default' => $context->getLanguage()->pipeList( $editWatchlistLinks ),
+                               'default' => $oouiEnabled ?
+                                       $editWatchlistLinks :
+                                       $context->getLanguage()->pipeList( $editWatchlistLinksOld ),
                                'label-message' => 'prefs-editwatchlist-label',
                                'section' => 'watchlist/editwatchlist',
                        ];
@@ -1191,13 +1250,31 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                $defaultPreferences['watchlisttoken'] = [
                        'type' => 'api',
                ];
-               $defaultPreferences['watchlisttoken-info'] = [
-                       'type' => 'info',
-                       'section' => 'watchlist/tokenwatchlist',
-                       'label-message' => 'prefs-watchlist-token',
-                       'default' => $user->getTokenFromOption( 'watchlisttoken' ),
-                       'help-message' => 'prefs-help-watchlist-token2',
-               ];
+
+               if ( $oouiEnabled ) {
+                       $tokenButton = new \OOUI\ButtonWidget( [
+                               'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [
+                                       'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
+                               ] ),
+                               'label' => $context->msg( 'prefs-watchlist-managetokens' )->text(),
+                       ] );
+                       $defaultPreferences['watchlisttoken-info'] = [
+                               'type' => 'info',
+                               'section' => 'watchlist/tokenwatchlist',
+                               'label-message' => 'prefs-watchlist-token',
+                               'help-message' => 'prefs-help-tokenmanagement',
+                               'raw' => true,
+                               'default' => (string)$tokenButton,
+                       ];
+               } else {
+                       $defaultPreferences['watchlisttoken-info'] = [
+                               'type' => 'info',
+                               'section' => 'watchlist/tokenwatchlist',
+                               'label-message' => 'prefs-watchlist-token',
+                               'default' => $user->getTokenFromOption( 'watchlisttoken' ),
+                               'help-message' => 'prefs-help-watchlist-token2',
+                       ];
+               }
        }
 
        /**
@@ -1406,14 +1483,19 @@ class DefaultPreferencesFactory implements PreferencesFactory {
         * @param IContextSource $context
         * @param string $formClass
         * @param array $remove Array of items to remove
-        * @return PreferencesForm|HTMLForm
+        * @return PreferencesForm
         */
        public function getForm(
                User $user,
                IContextSource $context,
-               $formClass = PreferencesForm::class,
+               $formClass = PreferencesFormOOUI::class,
                array $remove = []
        ) {
+               if ( SpecialPreferences::isOouiEnabled( $context ) ) {
+                       // We use ButtonWidgets in some of the getPreferences() functions
+                       $context->getOutput()->enableOOUI();
+               }
+
                $formDescriptor = $this->getFormDescriptor( $user, $context );
                if ( count( $remove ) ) {
                        $removeKeys = array_flip( $remove );
@@ -1642,17 +1724,25 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                $res = $this->saveFormData( $formData, $form );
 
                if ( $res ) {
+                       $context = $form->getContext();
+
                        $urlOptions = [];
 
                        if ( $res === 'eauth' ) {
                                $urlOptions['eauth'] = 1;
                        }
 
+                       if (
+                               $context->getRequest()->getFuzzyBool( 'ooui' ) !==
+                               $context->getConfig()->get( 'OOUIPreferences' )
+                       ) {
+                               $urlOptions[ 'ooui' ] = $context->getRequest()->getFuzzyBool( 'ooui' ) ? 1 : 0;
+                       }
+
                        $urlOptions += $form->getExtraSuccessRedirectParameters();
 
                        $url = $form->getTitle()->getFullURL( $urlOptions );
 
-                       $context = $form->getContext();
                        // Set session data for the success message
                        $context->getRequest()->getSession()->set( 'specialPreferencesSaveSuccess', 1 );
 
index fbd0a24..de25d32 100644 (file)
@@ -93,6 +93,9 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
        }
 
        /**
+        * Non-static proxy to ::getLogo (for overloading in sub classes or tests).
+        *
+        * @codeCoverageIgnore
         * @since 1.31
         * @param Config $conf
         * @return string|array
index 8e705c1..c98f7e3 100644 (file)
@@ -34,13 +34,13 @@ class SearchMySQL extends SearchDatabase {
        private static $mMinSearchLength;
 
        /**
-        * Parse the user's query and transform it into an SQL fragment which will
-        * become part of a WHERE clause
+        * Parse the user's query and transform it into two SQL fragments:
+        * a WHERE condition and an ORDER BY expression
         *
         * @param string $filteredText
         * @param string $fulltext
         *
-        * @return string
+        * @return array
         */
        function parseQuery( $filteredText, $fulltext ) {
                global $wgContLang;
@@ -127,7 +127,10 @@ class SearchMySQL extends SearchDatabase {
 
                $searchon = $this->db->addQuotes( $searchon );
                $field = $this->getIndexField( $fulltext );
-               return " MATCH($field) AGAINST($searchon IN BOOLEAN MODE) ";
+               return [
+                       " MATCH($field) AGAINST($searchon IN BOOLEAN MODE) ",
+                       " MATCH($field) AGAINST($searchon IN NATURAL LANGUAGE MODE) DESC "
+               ];
        }
 
        function regexTerm( $string, $wildcard ) {
@@ -303,7 +306,8 @@ class SearchMySQL extends SearchDatabase {
                $query['fields'][] = 'page_namespace';
                $query['fields'][] = 'page_title';
                $query['conds'][] = 'page_id=si_page';
-               $query['conds'][] = $match;
+               $query['conds'][] = $match[0];
+               $query['options']['ORDER BY'] = $match[1];
        }
 
        /**
@@ -318,7 +322,7 @@ class SearchMySQL extends SearchDatabase {
                $query = [
                        'tables' => [ 'page', 'searchindex' ],
                        'fields' => [ 'COUNT(*) as c' ],
-                       'conds' => [ 'page_id=si_page', $match ],
+                       'conds' => [ 'page_id=si_page', $match[0] ],
                        'options' => [],
                        'joins' => [],
                ];
index f3276e8..340bc2f 100644 (file)
@@ -166,24 +166,37 @@ abstract class Skin extends ContextSource {
         * It is recommended that skins wishing to override call parent::getDefaultModules()
         * and substitute out any modules they wish to change by using a key to look them up
         *
-        * For style modules, use setupSkinUserCss() instead.
+        * Any modules defined with the 'styles' key will be added as render blocking CSS via
+        * Output::addModuleStyles. Similarly, each key should refer to a list of modules
         *
         * @return array Array of modules with helper keys for easy overriding
         */
        public function getDefaultModules() {
                $out = $this->getOutput();
                $config = $this->getConfig();
-               $user = $out->getUser();
+               $user = $this->getUser();
+
+               // Modules declared in the $modules literal are loaded
+               // for ALL users, on ALL pages, in ALL skins.
+               // Keep this list as small as possible!
                $modules = [
-                       // modules not specific to any specific skin or page
+                       'styles' => [
+                               // The 'styles' key sets render-blocking style modules
+                               // Unlike other keys in $modules, this is an associative array
+                               // where each key is its own group pointing to a list of modules
+                               'core' => [
+                                       'mediawiki.legacy.shared',
+                                       'mediawiki.legacy.commonPrint',
+                               ],
+                               'content' => [],
+                               'syndicate' => [],
+                       ],
                        'core' => [
-                               // Enforce various default modules for all pages and all skins
-                               // Keep this list as small as possible
                                'site',
                                'mediawiki.page.startup',
                                'mediawiki.user',
                        ],
-                       // modules that enhance the page content in some way
+                       // modules that enhance the content in some way
                        'content' => [
                                'mediawiki.page.ready',
                        ],
@@ -193,6 +206,8 @@ abstract class Skin extends ContextSource {
                        'watch' => [],
                        // modules which relate to the current users preferences
                        'user' => [],
+                       // modules relating to RSS/Atom Feeds
+                       'syndicate' => [],
                ];
 
                // Support for high-density display images if enabled
@@ -203,11 +218,19 @@ abstract class Skin extends ContextSource {
                // Preload jquery.tablesorter for mediawiki.page.ready
                if ( strpos( $out->getHTML(), 'sortable' ) !== false ) {
                        $modules['content'][] = 'jquery.tablesorter';
+                       $modules['styles']['content'][] = 'jquery.tablesorter.styles';
                }
 
                // Preload jquery.makeCollapsible for mediawiki.page.ready
                if ( strpos( $out->getHTML(), 'mw-collapsible' ) !== false ) {
                        $modules['content'][] = 'jquery.makeCollapsible';
+                       $modules['styles']['content'][] = 'jquery.makeCollapsible.styles';
+               }
+
+               // Deprecated since 1.26: Unconditional loading of mediawiki.ui.button
+               // on every page is deprecated. Express a dependency instead.
+               if ( strpos( $out->getHTML(), 'mw-ui-button' ) !== false ) {
+                       $modules['styles']['content'][] = 'mediawiki.ui.button';
                }
 
                if ( $out->isTOCEnabled() ) {
@@ -232,6 +255,11 @@ abstract class Skin extends ContextSource {
                if ( $out->isArticle() && $user->getOption( 'editondblclick' ) ) {
                        $modules['user'][] = 'mediawiki.action.view.dblClickEdit';
                }
+
+               if ( $out->isSyndicated() ) {
+                       $modules['styles']['syndicate'][] = 'mediawiki.feedlink';
+               }
+
                return $modules;
        }
 
@@ -403,14 +431,14 @@ abstract class Skin extends ContextSource {
        }
 
        /**
-        * Add skin specific stylesheets
-        * Calling this method with an $out of anything but the same OutputPage
-        * inside ->getOutput() is deprecated. The $out arg is kept
-        * for compatibility purposes with skins.
-        * @param OutputPage $out
-        * @todo delete
+        * Hook point for adding style modules to OutputPage.
+        *
+        * @deprecated since 1.32 Use getDefaultModules() instead.
+        * @param OutputPage $out Legacy parameter, identical to $this->getOutput()
         */
-       abstract function setupSkinUserCss( OutputPage $out );
+       public function setupSkinUserCss( OutputPage $out ) {
+               // Stub.
+       }
 
        /**
         * TODO: document
index 4587533..1d5d534 100644 (file)
@@ -56,30 +56,6 @@ class SkinTemplate extends Skin {
        public $username;
        public $userpageUrlDetails;
 
-       /**
-        * Add specific styles for this skin
-        *
-        * @param OutputPage $out
-        */
-       public function setupSkinUserCss( OutputPage $out ) {
-               $moduleStyles = [
-                       'mediawiki.legacy.shared',
-                       'mediawiki.legacy.commonPrint',
-                       'mediawiki.sectionAnchor'
-               ];
-               if ( $out->isSyndicated() ) {
-                       $moduleStyles[] = 'mediawiki.feedlink';
-               }
-
-               // Deprecated since 1.26: Unconditional loading of mediawiki.ui.button
-               // on every page is deprecated. Express a dependency instead.
-               if ( strpos( $out->getHTML(), 'mw-ui-button' ) !== false ) {
-                       $moduleStyles[] = 'mediawiki.ui.button';
-               }
-
-               $out->addModuleStyles( $moduleStyles );
-       }
-
        /**
         * Create the template engine object; we feed it a bunch of data
         * and eventually it spits out some HTML. Should have interface
index 9028787..0c709af 100644 (file)
@@ -80,10 +80,12 @@ class SpecialActiveUsers extends SpecialPage {
        protected function buildForm() {
                $groups = User::getAllGroups();
 
+               $options = [];
                foreach ( $groups as $group ) {
                        $msg = htmlspecialchars( UserGroupMembership::getGroupName( $group ) );
                        $options[$msg] = $group;
                }
+               asort( $options );
 
                // Backwards-compatibility with old URLs
                $req = $this->getRequest();
index 4d2d1b9..e1909f5 100644 (file)
@@ -42,7 +42,6 @@ class SpecialAutoblockList extends SpecialPage {
                $this->setHeaders();
                $this->outputHeader();
                $out = $this->getOutput();
-               $lang = $this->getLanguage();
                $out->setPageTitle( $this->msg( 'autoblocklist' ) );
                $this->addHelpLink( 'Autoblock' );
                $out->addModuleStyles( [ 'mediawiki.special' ] );
@@ -55,13 +54,7 @@ class SpecialAutoblockList extends SpecialPage {
                        'Limit' => [
                                'type' => 'limitselect',
                                'label-message' => 'table_pager_limit_label',
-                               'options' => [
-                                       $lang->formatNum( 20 ) => 20,
-                                       $lang->formatNum( 50 ) => 50,
-                                       $lang->formatNum( 100 ) => 100,
-                                       $lang->formatNum( 250 ) => 250,
-                                       $lang->formatNum( 500 ) => 500,
-                               ],
+                               'options' => $pager->getLimitSelectList(),
                                'name' => 'limit',
                                'default' => $pager->getLimit(),
                        ]
index 667b814..186e5ad 100644 (file)
@@ -44,7 +44,6 @@ class SpecialBlockList extends SpecialPage {
                $this->setHeaders();
                $this->outputHeader();
                $out = $this->getOutput();
-               $lang = $this->getLanguage();
                $out->setPageTitle( $this->msg( 'ipblocklist' ) );
                $out->addModuleStyles( [ 'mediawiki.special' ] );
 
@@ -89,13 +88,7 @@ class SpecialBlockList extends SpecialPage {
                        'Limit' => [
                                'type' => 'limitselect',
                                'label-message' => 'table_pager_limit_label',
-                               'options' => [
-                                       $lang->formatNum( 20 ) => 20,
-                                       $lang->formatNum( 50 ) => 50,
-                                       $lang->formatNum( 100 ) => 100,
-                                       $lang->formatNum( 250 ) => 250,
-                                       $lang->formatNum( 500 ) => 500,
-                               ],
+                               'options' => $pager->getLimitSelectList(),
                                'name' => 'limit',
                                'default' => $pager->getLimit(),
                        ],
index f76c318..7b2d1bc 100644 (file)
@@ -107,6 +107,9 @@ class SpecialBotPasswords extends FormSpecialPage {
                                        'type' => 'check',
                                        'label-message' => 'botpasswords-label-resetpassword',
                                ];
+                               if ( $this->botPassword->isInvalid() ) {
+                                       $fields['resetPassword']['default'] = true;
+                               }
                        }
 
                        $lang = $this->getLanguage();
@@ -153,22 +156,39 @@ class SpecialBotPasswords extends FormSpecialPage {
 
                } else {
                        $linkRenderer = $this->getLinkRenderer();
+                       $passwordFactory = new PasswordFactory();
+                       $passwordFactory->init( $this->getConfig() );
+
                        $dbr = BotPassword::getDB( DB_REPLICA );
                        $res = $dbr->select(
                                'bot_passwords',
-                               [ 'bp_app_id' ],
+                               [ 'bp_app_id', 'bp_password' ],
                                [ 'bp_user' => $this->userId ],
                                __METHOD__
                        );
                        foreach ( $res as $row ) {
+                               try {
+                                       $password = $passwordFactory->newFromCiphertext( $row->bp_password );
+                                       $passwordInvalid = $password instanceof InvalidPassword;
+                                       unset( $password );
+                               } catch ( PasswordError $ex ) {
+                                       $passwordInvalid = true;
+                               }
+
+                               $text = $linkRenderer->makeKnownLink(
+                                       $this->getPageTitle( $row->bp_app_id ),
+                                       $row->bp_app_id
+                               );
+                               if ( $passwordInvalid ) {
+                                       $text .= $this->msg( 'word-separator' )->escaped()
+                                               . $this->msg( 'botpasswords-label-needsreset' )->parse();
+                               }
+
                                $fields[] = [
                                        'section' => 'existing',
                                        'type' => 'info',
                                        'raw' => true,
-                                       'default' => $linkRenderer->makeKnownLink(
-                                               $this->getPageTitle( $row->bp_app_id ),
-                                               $row->bp_app_id
-                                       ),
+                                       'default' => $text,
                                ];
                        }
 
index f702bc0..5e04d8d 100644 (file)
@@ -667,7 +667,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
                ];
                $context = new DerivativeContext( $this->getContext() );
                $context->setTitle( $this->getPageTitle( 'raw' ) ); // Reset subpage
-               $form = new HTMLForm( $fields, $context );
+               $form = new OOUIHTMLForm( $fields, $context );
                $form->setSubmitTextMsg( 'watchlistedit-raw-submit' );
                # Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit'
                $form->setSubmitTooltip( 'watchlistedit-raw-submit' );
@@ -686,7 +686,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
        protected function getClearForm() {
                $context = new DerivativeContext( $this->getContext() );
                $context->setTitle( $this->getPageTitle( 'clear' ) ); // Reset subpage
-               $form = new HTMLForm( [], $context );
+               $form = new OOUIHTMLForm( [], $context );
                $form->setSubmitTextMsg( 'watchlistedit-clear-submit' );
                # Used message keys: 'accesskey-watchlistedit-clear-submit', 'tooltip-watchlistedit-clear-submit'
                $form->setSubmitTooltip( 'watchlistedit-clear-submit' );
index 6a11bf4..bad1746 100644 (file)
@@ -51,6 +51,7 @@ class SpecialLog extends SpecialPage {
                $opts->add( 'dir', '' );
                $opts->add( 'offender', '' );
                $opts->add( 'subtype', '' );
+               $opts->add( 'logid', '' );
 
                // Set values
                $opts->fetchValuesFromRequest( $this->getRequest() );
@@ -169,6 +170,16 @@ class SpecialLog extends SpecialPage {
                return $subpages;
        }
 
+       /**
+        * Set options based on the subpage title parts:
+        * - One part that is a valid log type: Special:Log/logtype
+        * - Two parts: Special:Log/logtype/username
+        * - Otherwise, assume the whole subpage is a username.
+        *
+        * @param FormOptions $opts
+        * @param $par
+        * @throws ConfigException
+        */
        private function parseParams( FormOptions $opts, $par ) {
                # Get parameters
                $par = $par !== null ? $par : '';
@@ -204,7 +215,8 @@ class SpecialLog extends SpecialPage {
                        $opts->getValue( 'year' ),
                        $opts->getValue( 'month' ),
                        $opts->getValue( 'tagfilter' ),
-                       $opts->getValue( 'subtype' )
+                       $opts->getValue( 'subtype' ),
+                       $opts->getValue( 'logid' )
                );
 
                $this->addHeader( $opts->getValue( 'type' ) );
index 84292f3..7539235 100644 (file)
@@ -105,6 +105,8 @@ class SpecialPasswordReset extends FormSpecialPage {
        public function alterForm( HTMLForm $form ) {
                $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
 
+               $form->setSubmitDestructive();
+
                $form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
 
                $i = 0;
index a5c24e7..f67fe9f 100644 (file)
@@ -29,8 +29,26 @@ use MediaWiki\MediaWikiServices;
  * @ingroup SpecialPage
  */
 class SpecialPreferences extends SpecialPage {
+       /**
+        * @var bool Whether OOUI should be enabled here
+        */
+       private $oouiEnabled = false;
+
        function __construct() {
                parent::__construct( 'Preferences' );
+
+               $this->oouiEnabled = self::isOouiEnabled( $this->getContext() );
+       }
+
+       /**
+        * Check if OOUI mode is enabled, by config or query string
+        * @param IContextSource $context The context.
+        * @return bool
+        */
+       public static function isOouiEnabled( IContextSource $context ) {
+               return $context->getRequest()->getFuzzyBool( 'ooui',
+                       $context->getConfig()->get( 'OOUIPreferences' )
+               );
        }
 
        public function doesWrites() {
@@ -52,8 +70,13 @@ class SpecialPreferences extends SpecialPage {
                        return;
                }
 
-               $out->addModules( 'mediawiki.special.preferences' );
-               $out->addModuleStyles( 'mediawiki.special.preferences.styles' );
+               if ( $this->oouiEnabled ) {
+                       $out->addModules( 'mediawiki.special.preferences.ooui' );
+                       $out->addModuleStyles( 'mediawiki.special.preferences.styles.ooui' );
+               } else {
+                       $out->addModules( 'mediawiki.special.preferences' );
+                       $out->addModuleStyles( 'mediawiki.special.preferences.styles' );
+               }
 
                $session = $this->getRequest()->getSession();
                if ( $session->get( 'specialPreferencesSaveSuccess' ) ) {
@@ -86,35 +109,53 @@ class SpecialPreferences extends SpecialPage {
                $htmlForm = $this->getFormObject( $user, $this->getContext() );
                $sectionTitles = $htmlForm->getPreferenceSections();
 
-               $prefTabs = '';
-               foreach ( $sectionTitles as $key ) {
-                       $prefTabs .= Html::rawElement( 'li',
-                               [
-                                       'role' => 'presentation',
-                                       'class' => ( $key === 'personal' ) ? 'selected' : null
-                               ],
-                               Html::rawElement( 'a',
+               if ( $this->oouiEnabled ) {
+                       $prefTabs = [];
+                       foreach ( $sectionTitles as $key ) {
+                               $prefTabs[] = [
+                                       'name' => $key,
+                                       'label' => $htmlForm->getLegend( $key ),
+                               ];
+                       }
+                       $out->addJsConfigVars( 'wgPreferencesTabs', $prefTabs );
+
+                       // TODO: Render fake tabs here to avoid FOUC.
+                       // $out->addHTML( $fakeTabs );
+               } else {
+
+                       $prefTabs = '';
+                       foreach ( $sectionTitles as $key ) {
+                               $prefTabs .= Html::rawElement( 'li',
                                        [
-                                               'id' => 'preftab-' . $key,
-                                               'role' => 'tab',
-                                               'href' => '#mw-prefsection-' . $key,
-                                               'aria-controls' => 'mw-prefsection-' . $key,
-                                               'aria-selected' => ( $key === 'personal' ) ? 'true' : 'false',
-                                               'tabIndex' => ( $key === 'personal' ) ? 0 : -1,
+                                               'role' => 'presentation',
+                                               'class' => ( $key === 'personal' ) ? 'selected' : null
                                        ],
-                                       $htmlForm->getLegend( $key )
-                               )
+                                       Html::rawElement( 'a',
+                                               [
+                                                       'id' => 'preftab-' . $key,
+                                                       'role' => 'tab',
+                                                       'href' => '#mw-prefsection-' . $key,
+                                                       'aria-controls' => 'mw-prefsection-' . $key,
+                                                       'aria-selected' => ( $key === 'personal' ) ? 'true' : 'false',
+                                                       'tabIndex' => ( $key === 'personal' ) ? 0 : -1,
+                                               ],
+                                               $htmlForm->getLegend( $key )
+                                       )
+                               );
+                       }
+
+                       $out->addHTML(
+                               Html::rawElement( 'ul',
+                                       [
+                                               'id' => 'preftoc',
+                                               'role' => 'tablist'
+                                       ],
+                                       $prefTabs )
                        );
                }
 
-               $out->addHTML(
-                       Html::rawElement( 'ul',
-                               [
-                                       'id' => 'preftoc',
-                                       'role' => 'tablist'
-                               ],
-                               $prefTabs )
-               );
+               $htmlForm->addHiddenField( 'ooui', $this->oouiEnabled ? '1' : '0' );
+
                $htmlForm->show();
        }
 
@@ -126,7 +167,11 @@ class SpecialPreferences extends SpecialPage {
         */
        protected function getFormObject( $user, IContextSource $context ) {
                $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
-               $form = $preferencesFactory->getForm( $user, $context );
+               if ( $this->oouiEnabled ) {
+                       $form = $preferencesFactory->getForm( $user, $context, PreferencesFormOOUI::class );
+               } else {
+                       $form = $preferencesFactory->getForm( $user, $context, PreferencesFormLegacy::class );
+               }
                return $form;
        }
 
@@ -139,7 +184,9 @@ class SpecialPreferences extends SpecialPage {
 
                $context = new DerivativeContext( $this->getContext() );
                $context->setTitle( $this->getPageTitle( 'reset' ) ); // Reset subpage
-               $htmlForm = new HTMLForm( [], $context, 'prefs-restore' );
+               $htmlForm = HTMLForm::factory(
+                       $this->oouiEnabled ? 'ooui' : 'vform', [], $context, 'prefs-restore'
+               );
 
                $htmlForm->setSubmitTextMsg( 'restoreprefs' );
                $htmlForm->setSubmitDestructive();
index 36e7779..e827911 100644 (file)
@@ -162,7 +162,7 @@ class SpecialRedirect extends FormSpecialPage {
 
        /**
         * Handle Special:Redirect/logid/xxx
-        * (by redirecting to index.php?title=Special:Log)
+        * (by redirecting to index.php?title=Special:Log&logid=xxx)
         *
         * @since 1.27
         * @return string|null Url to redirect to, or null if $mValue is invalid.
@@ -176,80 +176,8 @@ class SpecialRedirect extends FormSpecialPage {
                if ( $logid === 0 ) {
                        return null;
                }
-
-               $logQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
-
-               $logparams = [
-                       'log_id' => 'log_id',
-                       'log_timestamp' => 'log_timestamp',
-                       'log_type' => 'log_type',
-                       'log_user_text' => $logQuery['fields']['log_user_text'],
-               ];
-
-               $dbr = wfGetDB( DB_REPLICA );
-
-               // Gets the nested SQL statement which
-               // returns timestamp of the log with the given log ID
-               $inner = $dbr->selectSQLText(
-                       'logging',
-                       [ 'log_timestamp' ],
-                       [ 'log_id' => $logid ]
-               );
-
-               // Returns all fields mentioned in $logparams of the logs
-               // with the same timestamp as the one returned by the statement above
-               $logsSameTimestamps = $dbr->select(
-                       [ 'logging' ] + $logQuery['tables'],
-                       $logparams,
-                       [ "log_timestamp = ($inner)" ],
-                       __METHOD__,
-                       [],
-                       $logQuery['joins']
-               );
-               if ( $logsSameTimestamps->numRows() === 0 ) {
-                       return null;
-               }
-
-               // Stores the row with the same log ID as the one given
-               $rowMain = [];
-               foreach ( $logsSameTimestamps as $row ) {
-                       if ( (int)$row->log_id === $logid ) {
-                               $rowMain = $row;
-                       }
-               }
-
-               array_shift( $logparams );
-
-               // Stores all the rows with the same values in each column
-               // as $rowMain
-               foreach ( $logparams as $key => $dummy ) {
-                       $matchedRows = [];
-                       foreach ( $logsSameTimestamps as $row ) {
-                               if ( $row->$key === $rowMain->$key ) {
-                                       $matchedRows[] = $row;
-                               }
-                       }
-                       if ( count( $matchedRows ) === 1 ) {
-                               break;
-                       }
-                       $logsSameTimestamps = $matchedRows;
-               }
-               $query = [ 'title' => 'Special:Log', 'limit' => count( $matchedRows ) ];
-
-               // A map of database field names from table 'logging' to the values of $logparams
-               $keys = [
-                       'log_timestamp' => 'offset',
-                       'log_type' => 'type',
-                       'log_user_text' => 'user'
-               ];
-
-               foreach ( $logparams as $logKey => $dummy ) {
-                       $query[$keys[$logKey]] = $matchedRows[0]->$logKey;
-               }
-               $query['offset'] = $query['offset'] + 1;
-               $url = $query;
-
-               return wfAppendQuery( wfScript( 'index' ), $url );
+               $query = [ 'title' => 'Special:Log', 'logid' => $logid ];
+               return wfAppendQuery( wfScript( 'index' ), $query );
        }
 
        /**
index 964a261..d5b0903 100644 (file)
@@ -121,6 +121,7 @@ class SpecialResetTokens extends FormSpecialPage {
         * @param HTMLForm $form
         */
        protected function alterForm( HTMLForm $form ) {
+               $form->setSubmitDestructive();
                if ( $this->getTokensList() ) {
                        $form->setSubmitTextMsg( 'resettokens-resetbutton' );
                } else {
index 146e6e7..d5e14d2 100644 (file)
@@ -170,7 +170,7 @@ class SpecialStatistics extends SpecialPage {
                        Xml::closeElement( 'tr' ) .
                        $this->formatRow( $this->msg( 'statistics-users' )->parse() . ' ' .
                                $this->getLinkRenderer()->makeKnownLink(
-                                       SpecialPage::getTitleFor( 'ListUsers' ),
+                                       SpecialPage::getTitleFor( 'Listusers' ),
                                        $this->msg( 'listgrouprights-members' )->text()
                                ),
                                $this->getLanguage()->formatNum( $this->users ),
index fe9202e..b2d5a16 100644 (file)
@@ -57,6 +57,7 @@ class SpecialUnblock extends SpecialPage {
 
                $out = $this->getOutput();
                $out->setPageTitle( $this->msg( 'unblockip' ) );
+               $out->addModules( [ 'mediawiki.userSuggest' ] );
 
                $form = HTMLForm::factory( 'ooui', $this->getFields(), $this->getContext() );
                $form->setWrapperLegendMsg( 'unblockip' );
@@ -86,11 +87,12 @@ class SpecialUnblock extends SpecialPage {
        protected function getFields() {
                $fields = [
                        'Target' => [
-                               'type' => 'user',
+                               'type' => 'text',
                                'label-message' => 'ipaddressorusername',
                                'autofocus' => true,
                                'size' => '45',
                                'required' => true,
+                               'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
                        ],
                        'Name' => [
                                'type' => 'info',
index 6d6bf0e..a05452d 100644 (file)
@@ -882,7 +882,7 @@ class UserrightsPage extends SpecialPage {
                                                }
                                                // T171345: Add a hidden form element so that other groups can still be manipulated,
                                                // otherwise saving errors out with an invalid expiry time for this group.
-                                               $expiryHtml .= Html::Hidden( "wpExpiry-$group",
+                                               $expiryHtml .= Html::hidden( "wpExpiry-$group",
                                                        $currentExpiry ? 'existing' : 'infinite' );
                                                $expiryHtml .= "<br />\n";
                                        } else {
index 931cd24..a2f3128 100644 (file)
@@ -57,9 +57,25 @@ class Licenses extends HTMLFormField {
         * @return string
         */
        protected static function getMessageFromParams( $params ) {
-               return empty( $params['licenses'] )
-                       ? wfMessage( 'licenses' )->inContentLanguage()->plain()
-                       : $params['licenses'];
+               global $wgContLang;
+
+               if ( !empty( $params['licenses'] ) ) {
+                       return $params['licenses'];
+               }
+
+               // If the licenses page is in $wgForceUIMsgAsContentMsg (which is the case
+               // on Commons), translations will be in the database, in subpages of this
+               // message (e.g. MediaWiki:Licenses/<lang>)
+               // If there is no such translation, the result will be '-' (the empty default
+               // in the i18n files), so we'll need to force it to look up the actual licenses
+               // in the default site language (= get the translation from MediaWiki:Licenses)
+               // Also see https://phabricator.wikimedia.org/T3495
+               $defaultMsg = wfMessage( 'licenses' )->inContentLanguage();
+               if ( !$defaultMsg->exists() || $defaultMsg->plain() === '-' ) {
+                       $defaultMsg = wfMessage( 'licenses' )->inLanguage( $wgContLang );
+               }
+
+               return $defaultMsg->plain();
        }
 
        /**
index 723093a..b60882a 100644 (file)
@@ -19,9 +19,9 @@
  */
 
 /**
- * Extend HTMLForm purely so we can have a more sane way of getting the section headers
+ * Extend OOUIHTMLForm purely so we can have a more sane way of getting the section headers
  */
-class EditWatchlistNormalHTMLForm extends HTMLForm {
+class EditWatchlistNormalHTMLForm extends OOUIHTMLForm {
        public function getLegend( $namespace ) {
                $namespace = substr( $namespace, 2 );
 
@@ -29,8 +29,4 @@ class EditWatchlistNormalHTMLForm extends HTMLForm {
                        ? $this->msg( 'blanknamespace' )->escaped()
                        : htmlspecialchars( $this->getContext()->getLanguage()->getFormattedNsText( $namespace ) );
        }
-
-       public function getBody() {
-               return $this->displaySection( $this->mFieldTree, '', 'editwatchlist-' );
-       }
 }
index d4e5ef4..a124410 100644 (file)
  * @file
  */
 
-use MediaWiki\MediaWikiServices;
-
 /**
- * Form to edit user preferences.
+ * Temporarily define PreferencesForm as an interface, so PreferencesFormOOUI
+ * and PreferencesFormLegacy can implement it.
+ *
+ * When PreferencesFormLegacy we can merge PreferencesFormOOUI with PreferencesForm.
  */
-class PreferencesForm extends HTMLForm {
-       // Override default value from HTMLForm
-       protected $mSubSectionBeforeFields = false;
-
-       private $modifiedUser;
-
-       /**
-        * @param User $user
-        */
-       public function setModifiedUser( $user ) {
-               $this->modifiedUser = $user;
-       }
-
-       /**
-        * @return User
-        */
-       public function getModifiedUser() {
-               if ( $this->modifiedUser === null ) {
-                       return $this->getUser();
-               } else {
-                       return $this->modifiedUser;
-               }
-       }
-
-       /**
-        * Get extra parameters for the query string when redirecting after
-        * successful save.
-        *
-        * @return array
-        */
-       public function getExtraSuccessRedirectParameters() {
-               return [];
-       }
-
-       /**
-        * @param string $html
-        * @return string
-        */
-       function wrapForm( $html ) {
-               $html = Xml::tags( 'div', [ 'id' => 'preferences' ], $html );
-
-               return parent::wrapForm( $html );
-       }
-
-       /**
-        * @return string
-        */
-       function getButtons() {
-               $attrs = [ 'id' => 'mw-prefs-restoreprefs' ];
-
-               if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
-                       return '';
-               }
-
-               $html = parent::getButtons();
-
-               if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
-                       $t = $this->getTitle()->getSubpage( 'reset' );
-
-                       $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
-                       $html .= "\n" . $linkRenderer->makeLink( $t, $this->msg( 'restoreprefs' )->text(),
-                               Html::buttonAttributes( $attrs, [ 'mw-ui-quiet' ] ) );
-
-                       $html = Xml::tags( 'div', [ 'class' => 'mw-prefs-buttons' ], $html );
-               }
-
-               return $html;
-       }
-
-       /**
-        * Separate multi-option preferences into multiple preferences, since we
-        * have to store them separately
-        * @param array $data
-        * @return array
-        */
-       function filterDataForSubmit( $data ) {
-               foreach ( $this->mFlatFields as $fieldname => $field ) {
-                       if ( $field instanceof HTMLNestedFilterable ) {
-                               $info = $field->mParams;
-                               $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
-                               foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) {
-                                       $data["$prefix$key"] = $value;
-                               }
-                               unset( $data[$fieldname] );
-                       }
-               }
-
-               return $data;
-       }
-
-       /**
-        * Get the whole body of the form.
-        * @return string
-        */
-       function getBody() {
-               return $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' );
-       }
-
-       /**
-        * Get the "<legend>" for a given section key. Normally this is the
-        * prefs-$key message but we'll allow extensions to override it.
-        * @param string $key
-        * @return string
-        */
-       function getLegend( $key ) {
-               $legend = parent::getLegend( $key );
-               Hooks::run( 'PreferencesGetLegend', [ $this, $key, &$legend ] );
-               return $legend;
-       }
-
-       /**
-        * Get the keys of each top level preference section.
-        * @return array of section keys
-        */
-       function getPreferenceSections() {
-               return array_keys( array_filter( $this->mFieldTree, 'is_array' ) );
-       }
+interface PreferencesForm {
 }
diff --git a/includes/specials/forms/PreferencesFormLegacy.php b/includes/specials/forms/PreferencesFormLegacy.php
new file mode 100644 (file)
index 0000000..e6bc494
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Form to edit user preferences.
+ */
+class PreferencesFormLegacy extends HTMLForm implements PreferencesForm {
+       // Override default value from HTMLForm
+       protected $mSubSectionBeforeFields = false;
+
+       private $modifiedUser;
+
+       /**
+        * @param User $user
+        */
+       public function setModifiedUser( $user ) {
+               $this->modifiedUser = $user;
+       }
+
+       /**
+        * @return User
+        */
+       public function getModifiedUser() {
+               if ( $this->modifiedUser === null ) {
+                       return $this->getUser();
+               } else {
+                       return $this->modifiedUser;
+               }
+       }
+
+       /**
+        * Get extra parameters for the query string when redirecting after
+        * successful save.
+        *
+        * @return array
+        */
+       public function getExtraSuccessRedirectParameters() {
+               return [];
+       }
+
+       /**
+        * @param string $html
+        * @return string
+        */
+       function wrapForm( $html ) {
+               $html = Xml::tags( 'div', [ 'id' => 'preferences' ], $html );
+
+               return parent::wrapForm( $html );
+       }
+
+       /**
+        * @return string
+        */
+       function getButtons() {
+               $attrs = [ 'id' => 'mw-prefs-restoreprefs' ];
+
+               if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
+                       return '';
+               }
+
+               $html = parent::getButtons();
+
+               if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
+                       $t = $this->getTitle()->getSubpage( 'reset' );
+
+                       $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+                       $html .= "\n" . $linkRenderer->makeLink( $t, $this->msg( 'restoreprefs' )->text(),
+                               Html::buttonAttributes( $attrs, [ 'mw-ui-quiet' ] ) );
+
+                       $html = Xml::tags( 'div', [ 'class' => 'mw-prefs-buttons' ], $html );
+               }
+
+               return $html;
+       }
+
+       /**
+        * Separate multi-option preferences into multiple preferences, since we
+        * have to store them separately
+        * @param array $data
+        * @return array
+        */
+       function filterDataForSubmit( $data ) {
+               foreach ( $this->mFlatFields as $fieldname => $field ) {
+                       if ( $field instanceof HTMLNestedFilterable ) {
+                               $info = $field->mParams;
+                               $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
+                               foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) {
+                                       $data["$prefix$key"] = $value;
+                               }
+                               unset( $data[$fieldname] );
+                       }
+               }
+
+               return $data;
+       }
+
+       /**
+        * Get the whole body of the form.
+        * @return string
+        */
+       function getBody() {
+               return $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' );
+       }
+
+       /**
+        * Get the "<legend>" for a given section key. Normally this is the
+        * prefs-$key message but we'll allow extensions to override it.
+        * @param string $key
+        * @return string
+        */
+       function getLegend( $key ) {
+               $legend = parent::getLegend( $key );
+               Hooks::run( 'PreferencesGetLegend', [ $this, $key, &$legend ] );
+               return $legend;
+       }
+
+       /**
+        * Get the keys of each top level preference section.
+        * @return array of section keys
+        */
+       function getPreferenceSections() {
+               return array_keys( array_filter( $this->mFieldTree, 'is_array' ) );
+       }
+}
diff --git a/includes/specials/forms/PreferencesFormOOUI.php b/includes/specials/forms/PreferencesFormOOUI.php
new file mode 100644 (file)
index 0000000..a781254
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Form to edit user preferences.
+ */
+class PreferencesFormOOUI extends OOUIHTMLForm implements PreferencesForm {
+       // Override default value from HTMLForm
+       protected $mSubSectionBeforeFields = false;
+
+       private $modifiedUser;
+
+       /**
+        * @param User $user
+        */
+       public function setModifiedUser( $user ) {
+               $this->modifiedUser = $user;
+       }
+
+       /**
+        * @return User
+        */
+       public function getModifiedUser() {
+               if ( $this->modifiedUser === null ) {
+                       return $this->getUser();
+               } else {
+                       return $this->modifiedUser;
+               }
+       }
+
+       /**
+        * Get extra parameters for the query string when redirecting after
+        * successful save.
+        *
+        * @return array
+        */
+       public function getExtraSuccessRedirectParameters() {
+               return [];
+       }
+
+       /**
+        * @param string $html
+        * @return string
+        */
+       function wrapForm( $html ) {
+               $html = Xml::tags( 'div', [ 'id' => 'preferences' ], $html );
+
+               return parent::wrapForm( $html );
+       }
+
+       /**
+        * @return string
+        */
+       function getButtons() {
+               if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
+                       return '';
+               }
+
+               $html = parent::getButtons();
+
+               if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
+                       $t = $this->getTitle()->getSubpage( 'reset' );
+
+                       $html .= new OOUI\ButtonWidget( [
+                               'infusable' => true,
+                               'id' => 'mw-prefs-restoreprefs',
+                               'label' => $this->msg( 'restoreprefs' )->text(),
+                               'href' => $t->getLinkURL(),
+                               'flags' => [ 'destructive' ],
+                               'framed' => false,
+                       ] );
+
+                       $html = Xml::tags( 'div', [ 'class' => 'mw-prefs-buttons' ], $html );
+               }
+
+               return $html;
+       }
+
+       /**
+        * Separate multi-option preferences into multiple preferences, since we
+        * have to store them separately
+        * @param array $data
+        * @return array
+        */
+       function filterDataForSubmit( $data ) {
+               foreach ( $this->mFlatFields as $fieldname => $field ) {
+                       if ( $field instanceof HTMLNestedFilterable ) {
+                               $info = $field->mParams;
+                               $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
+                               foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) {
+                                       $data["$prefix$key"] = $value;
+                               }
+                               unset( $data[$fieldname] );
+                       }
+               }
+
+               return $data;
+       }
+
+       /**
+        * Get the whole body of the form.
+        * @return string
+        */
+       function getBody() {
+               return $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' );
+       }
+
+       /**
+        * Get the "<legend>" for a given section key. Normally this is the
+        * prefs-$key message but we'll allow extensions to override it.
+        * @param string $key
+        * @return string
+        */
+       function getLegend( $key ) {
+               $legend = parent::getLegend( $key );
+               Hooks::run( 'PreferencesGetLegend', [ $this, $key, &$legend ] );
+               return $legend;
+       }
+
+       /**
+        * Get the keys of each top level preference section.
+        * @return array of section keys
+        */
+       function getPreferenceSections() {
+               return array_keys( array_filter( $this->mFieldTree, 'is_array' ) );
+       }
+}
index 4b5fe19..b2f1487 100644 (file)
@@ -519,8 +519,8 @@ class ImageListPager extends TablePager {
        }
 
        function getForm() {
-               $fields = [];
-               $fields['limit'] = [
+               $formDescriptor = [];
+               $formDescriptor['limit'] = [
                        'type' => 'select',
                        'name' => 'limit',
                        'label-message' => 'table_pager_limit_label',
@@ -529,7 +529,7 @@ class ImageListPager extends TablePager {
                ];
 
                if ( !$this->getConfig()->get( 'MiserMode' ) ) {
-                       $fields['ilsearch'] = [
+                       $formDescriptor['ilsearch'] = [
                                'type' => 'text',
                                'name' => 'ilsearch',
                                'id' => 'mw-ilsearch',
@@ -540,7 +540,7 @@ class ImageListPager extends TablePager {
                        ];
                }
 
-               $fields['user'] = [
+               $formDescriptor['user'] = [
                        'type' => 'user',
                        'name' => 'user',
                        'id' => 'mw-listfiles-user',
@@ -550,7 +550,7 @@ class ImageListPager extends TablePager {
                        'maxlength' => '255',
                ];
 
-               $fields['ilshowall'] = [
+               $formDescriptor['ilshowall'] = [
                        'type' => 'check',
                        'name' => 'ilshowall',
                        'id' => 'mw-listfiles-show-all',
@@ -565,17 +565,16 @@ class ImageListPager extends TablePager {
                unset( $query['ilshowall'] );
                unset( $query['user'] );
 
-               $form = new HTMLForm( $fields, $this->getContext() );
-
-               $form->setMethod( 'get' );
-               $form->setTitle( $this->getTitle() );
-               $form->setId( 'mw-listfiles-form' );
-               $form->setWrapperLegendMsg( 'listfiles' );
-               $form->setSubmitTextMsg( 'table_pager_limit_submit' );
-               $form->addHiddenFields( $query );
-
-               $form->prepareForm();
-               $form->displayForm( '' );
+               $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
+               $htmlForm
+                       ->setMethod( 'get' )
+                       ->setId( 'mw-listfiles-form' )
+                       ->setTitle( $this->getTitle() )
+                       ->setSubmitTextMsg( 'table_pager_limit_submit' )
+                       ->setWrapperLegendMsg( 'listfiles' )
+                       ->addHiddenFields( $query )
+                       ->prepareForm()
+                       ->displayForm( '' );
        }
 
        protected function getTableClass() {
diff --git a/includes/tidy/Balancer.php b/includes/tidy/Balancer.php
deleted file mode 100644 (file)
index 6671f49..0000000
+++ /dev/null
@@ -1,3584 +0,0 @@
-<?php
-/**
- * An implementation of the tree building portion of the HTML5 parsing
- * spec.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Parser
- * @since 1.27
- * @author C. Scott Ananian, 2016
- */
-
-namespace MediaWiki\Tidy;
-
-use ExplodeIterator;
-use IteratorAggregate;
-use ReverseArrayIterator;
-use Sanitizer;
-use Wikimedia\Assert\Assert;
-use Wikimedia\Assert\ParameterAssertionException;
-
-// A note for future librarization[1] -- this file is a good candidate
-// for splitting into an independent library, except that it is currently
-// highly optimized for MediaWiki use.  It only implements the portions
-// of the HTML5 tree builder used by tags supported by MediaWiki, and
-// does not contain a true tokenizer pass, instead relying on
-// comment stripping, attribute normalization, and escaping done by
-// the MediaWiki Sanitizer.  It also deliberately avoids building
-// a true DOM in memory, instead serializing elements to an output string
-// as soon as possible (usually as soon as the tag is closed) to reduce
-// its memory footprint.
-
-// We've been gradually lifting some of these restrictions to handle
-// non-sanitized output generated by extensions, but we shortcut the tokenizer
-// for speed (primarily by splitting on `<`) and so rely on syntactic
-// well-formedness.
-
-// On the other hand, I've been pretty careful to note with comments in the
-// code the places where this implementation omits features of the spec or
-// depends on the MediaWiki Sanitizer.  Perhaps in the future we'll want to
-// implement the missing pieces and make this a standalone PHP HTML5 parser.
-// In order to do so, some sort of MediaWiki-specific API will need
-// to be added to (a) allow the Balancer to bypass the tokenizer,
-// and (b) support on-the-fly flattening instead of DOM node creation.
-
-// [1]: https://www.mediawiki.org/wiki/Library_infrastructure_for_MediaWiki
-
-/**
- * Utility constants and sets for the HTML5 tree building algorithm.
- * Sets are associative arrays indexed first by namespace and then by
- * lower-cased tag name.
- *
- * @ingroup Parser
- * @since 1.27
- */
-class BalanceSets {
-       const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
-       const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
-       const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
-
-       public static $unsupportedSet = [
-               self::HTML_NAMESPACE => [
-                       'html' => true, 'head' => true, 'body' => true, 'frameset' => true,
-                       'frame' => true,
-                       'plaintext' => true,
-                       'xmp' => true, 'iframe' => true, 'noembed' => true,
-                       'noscript' => true, 'script' => true,
-                       'title' => true
-               ]
-       ];
-
-       public static $emptyElementSet = [
-               self::HTML_NAMESPACE => [
-                       'area' => true, 'base' => true, 'basefont' => true,
-                       'bgsound' => true, 'br' => true, 'col' => true, 'command' => true,
-                       'embed' => true, 'frame' => true, 'hr' => true, 'img' => true,
-                       'input' => true, 'keygen' => true, 'link' => true, 'meta' => true,
-                       'param' => true, 'source' => true, 'track' => true, 'wbr' => true
-               ]
-       ];
-
-       public static $extraLinefeedSet = [
-               self::HTML_NAMESPACE => [
-                       'pre' => true, 'textarea' => true, 'listing' => true,
-               ]
-       ];
-
-       public static $headingSet = [
-               self::HTML_NAMESPACE => [
-                       'h1' => true, 'h2' => true, 'h3' => true,
-                       'h4' => true, 'h5' => true, 'h6' => true
-               ]
-       ];
-
-       public static $specialSet = [
-               self::HTML_NAMESPACE => [
-                       'address' => true, 'applet' => true, 'area' => true,
-                       'article' => true, 'aside' => true, 'base' => true,
-                       'basefont' => true, 'bgsound' => true, 'blockquote' => true,
-                       'body' => true, 'br' => true, 'button' => true, 'caption' => true,
-                       'center' => true, 'col' => true, 'colgroup' => true, 'dd' => true,
-                       'details' => true, 'dir' => true, 'div' => true, 'dl' => true,
-                       'dt' => true, 'embed' => true, 'fieldset' => true,
-                       'figcaption' => true, 'figure' => true, 'footer' => true,
-                       'form' => true, 'frame' => true, 'frameset' => true, 'h1' => true,
-                       'h2' => true, 'h3' => true, 'h4' => true, 'h5' => true,
-                       'h6' => true, 'head' => true, 'header' => true, 'hgroup' => true,
-                       'hr' => true, 'html' => true, 'iframe' => true, 'img' => true,
-                       'input' => true, 'li' => true, 'link' => true,
-                       'listing' => true, 'main' => true, 'marquee' => true,
-                       'menu' => true, 'meta' => true, 'nav' => true,
-                       'noembed' => true, 'noframes' => true, 'noscript' => true,
-                       'object' => true, 'ol' => true, 'p' => true, 'param' => true,
-                       'plaintext' => true, 'pre' => true, 'script' => true,
-                       'section' => true, 'select' => true, 'source' => true,
-                       'style' => true, 'summary' => true, 'table' => true,
-                       'tbody' => true, 'td' => true, 'template' => true,
-                       'textarea' => true, 'tfoot' => true, 'th' => true, 'thead' => true,
-                       'title' => true, 'tr' => true, 'track' => true, 'ul' => true,
-                       'wbr' => true, 'xmp' => true
-               ],
-               self::SVG_NAMESPACE => [
-                       'foreignobject' => true, 'desc' => true, 'title' => true
-               ],
-               self::MATHML_NAMESPACE => [
-                       'mi' => true, 'mo' => true, 'mn' => true, 'ms' => true,
-                       'mtext' => true, 'annotation-xml' => true
-               ]
-       ];
-
-       public static $addressDivPSet = [
-               self::HTML_NAMESPACE => [
-                       'address' => true, 'div' => true, 'p' => true
-               ]
-       ];
-
-       public static $tableSectionRowSet = [
-               self::HTML_NAMESPACE => [
-                       'table' => true, 'thead' => true, 'tbody' => true,
-                       'tfoot' => true, 'tr' => true
-               ]
-       ];
-
-       public static $impliedEndTagsSet = [
-               self::HTML_NAMESPACE => [
-                       'dd' => true, 'dt' => true, 'li' => true,
-                       'menuitem' => true, 'optgroup' => true,
-                       'option' => true, 'p' => true, 'rb' => true, 'rp' => true,
-                       'rt' => true, 'rtc' => true
-               ]
-       ];
-
-       public static $thoroughImpliedEndTagsSet = [
-               self::HTML_NAMESPACE => [
-                       'caption' => true, 'colgroup' => true, 'dd' => true, 'dt' => true,
-                       'li' => true, 'optgroup' => true, 'option' => true, 'p' => true,
-                       'rb' => true, 'rp' => true, 'rt' => true, 'rtc' => true,
-                       'tbody' => true, 'td' => true, 'tfoot' => true, 'th' => true,
-                       'thead' => true, 'tr' => true
-               ]
-       ];
-
-       public static $tableCellSet = [
-               self::HTML_NAMESPACE => [
-                       'td' => true, 'th' => true
-               ]
-       ];
-       public static $tableContextSet = [
-               self::HTML_NAMESPACE => [
-                       'table' => true, 'template' => true, 'html' => true
-               ]
-       ];
-
-       public static $tableBodyContextSet = [
-               self::HTML_NAMESPACE => [
-                       'tbody' => true, 'tfoot' => true, 'thead' => true,
-                       'template' => true, 'html' => true
-               ]
-       ];
-
-       public static $tableRowContextSet = [
-               self::HTML_NAMESPACE => [
-                       'tr' => true, 'template' => true, 'html' => true
-               ]
-       ];
-
-       // See https://html.spec.whatwg.org/multipage/forms.html#form-associated-element
-       public static $formAssociatedSet = [
-               self::HTML_NAMESPACE => [
-                       'button' => true, 'fieldset' => true, 'input' => true,
-                       'keygen' => true, 'object' => true, 'output' => true,
-                       'select' => true, 'textarea' => true, 'img' => true
-               ]
-       ];
-
-       public static $inScopeSet = [
-               self::HTML_NAMESPACE => [
-                       'applet' => true, 'caption' => true, 'html' => true,
-                       'marquee' => true, 'object' => true,
-                       'table' => true, 'td' => true, 'template' => true,
-                       'th' => true
-               ],
-               self::SVG_NAMESPACE => [
-                       'foreignobject' => true, 'desc' => true, 'title' => true
-               ],
-               self::MATHML_NAMESPACE => [
-                       'mi' => true, 'mo' => true, 'mn' => true, 'ms' => true,
-                       'mtext' => true, 'annotation-xml' => true
-               ]
-       ];
-
-       private static $inListItemScopeSet = null;
-       public static function inListItemScopeSet() {
-               if ( self::$inListItemScopeSet === null ) {
-                       self::$inListItemScopeSet = self::$inScopeSet;
-                       self::$inListItemScopeSet[self::HTML_NAMESPACE]['ol'] = true;
-                       self::$inListItemScopeSet[self::HTML_NAMESPACE]['ul'] = true;
-               }
-               return self::$inListItemScopeSet;
-       }
-
-       private static $inButtonScopeSet = null;
-       public static function inButtonScopeSet() {
-               if ( self::$inButtonScopeSet === null ) {
-                       self::$inButtonScopeSet = self::$inScopeSet;
-                       self::$inButtonScopeSet[self::HTML_NAMESPACE]['button'] = true;
-               }
-               return self::$inButtonScopeSet;
-       }
-
-       public static $inTableScopeSet = [
-               self::HTML_NAMESPACE => [
-                       'html' => true, 'table' => true, 'template' => true
-               ]
-       ];
-
-       public static $inInvertedSelectScopeSet = [
-               self::HTML_NAMESPACE => [
-                       'option' => true, 'optgroup' => true
-               ]
-       ];
-
-       public static $mathmlTextIntegrationPointSet = [
-               self::MATHML_NAMESPACE => [
-                       'mi' => true, 'mo' => true, 'mn' => true, 'ms' => true,
-                       'mtext' => true
-               ]
-       ];
-
-       public static $htmlIntegrationPointSet = [
-               self::SVG_NAMESPACE => [
-                       'foreignobject' => true,
-                       'desc' => true,
-                       'title' => true
-               ]
-       ];
-
-       // For tidy compatibility.
-       public static $tidyPWrapSet = [
-               self::HTML_NAMESPACE => [
-                       'body' => true, 'blockquote' => true,
-                       // We parse with <body> as the fragment context, but the top-level
-                       // element on the stack is actually <html>.  We could use the
-                       // "adjusted current node" everywhere to work around this, but it's
-                       // easier just to add <html> to the p-wrap set.
-                       'html' => true,
-               ],
-       ];
-       public static $tidyInlineSet = [
-               self::HTML_NAMESPACE => [
-                       'a' => true, 'abbr' => true, 'acronym' => true, 'applet' => true,
-                       'b' => true, 'basefont' => true, 'bdo' => true, 'big' => true,
-                       'br' => true, 'button' => true, 'cite' => true, 'code' => true,
-                       'dfn' => true, 'em' => true, 'font' => true, 'i' => true,
-                       'iframe' => true, 'img' => true, 'input' => true, 'kbd' => true,
-                       'label' => true, 'legend' => true, 'map' => true, 'object' => true,
-                       'param' => true, 'q' => true, 'rb' => true, 'rbc' => true,
-                       'rp' => true, 'rt' => true, 'rtc' => true, 'ruby' => true,
-                       's' => true, 'samp' => true, 'select' => true, 'small' => true,
-                       'span' => true, 'strike' => true, 'strong' => true, 'sub' => true,
-                       'sup' => true, 'textarea' => true, 'tt' => true, 'u' => true,
-                       'var' => true,
-                       // Those defined in tidy.conf
-                       'video' => true, 'audio' => true, 'bdi' => true, 'data' => true,
-                       'time' => true, 'mark' => true,
-               ],
-       ];
-}
-
-/**
- * A BalanceElement is a simplified version of a DOM Node.  The main
- * difference is that we only keep BalanceElements around for nodes
- * currently on the BalanceStack of open elements.  As soon as an
- * element is closed, with some minor exceptions relating to the
- * tree builder "adoption agency algorithm", the element and all its
- * children are serialized to a string using the flatten() method.
- * This keeps our memory usage low.
- *
- * @ingroup Parser
- * @since 1.27
- */
-class BalanceElement {
-       /**
-        * The namespace of the element.
-        * @var string $namespaceURI
-        */
-       public $namespaceURI;
-       /**
-        * The lower-cased name of the element.
-        * @var string $localName
-        */
-       public $localName;
-       /**
-        * Attributes for the element, in array form
-        * @var array $attribs
-        */
-       public $attribs;
-
-       /**
-        * Parent of this element, or the string "flat" if this element has
-        * already been flattened into its parent.
-        * @var BalanceElement|string|null $parent
-        */
-       public $parent;
-
-       /**
-        * An array of children of this element.  Typically only the last
-        * child will be an actual BalanceElement object; the rest will
-        * be strings, representing either text nodes or flattened
-        * BalanceElement objects.
-        * @var BalanceElement[]|string[] $children
-        */
-       public $children;
-
-       /**
-        * A unique string identifier for Noah's Ark purposes, lazy initialized
-        */
-       private $noahKey;
-
-       /**
-        * The next active formatting element in the list, or null if this is the
-        * end of the AFE list or if the element is not in the AFE list.
-        */
-       public $nextAFE;
-
-       /**
-        * The previous active formatting element in the list, or null if this is
-        * the start of the list or if the element is not in the AFE list.
-        */
-       public $prevAFE;
-
-       /**
-        * The next element in the Noah's Ark species bucket.
-        */
-       public $nextNoah;
-
-       /**
-        * Make a new BalanceElement corresponding to the HTML DOM Element
-        * with the given localname, namespace, and attributes.
-        *
-        * @param string $namespaceURI The namespace of the element.
-        * @param string $localName The lowercased name of the tag.
-        * @param array $attribs Attributes of the element
-        */
-       public function __construct( $namespaceURI, $localName, array $attribs ) {
-               $this->localName = $localName;
-               $this->namespaceURI = $namespaceURI;
-               $this->attribs = $attribs;
-               $this->contents = '';
-               $this->parent = null;
-               $this->children = [];
-       }
-
-       /**
-        * Remove the given child from this element.
-        * @param BalanceElement $elt
-        */
-       private function removeChild( BalanceElement $elt ) {
-               Assert::precondition(
-                       $this->parent !== 'flat', "Can't removeChild after flattening $this"
-               );
-               Assert::parameter(
-                       $elt->parent === $this, 'elt', 'must have $this as a parent'
-               );
-               $idx = array_search( $elt, $this->children, true );
-               Assert::parameter( $idx !== false, '$elt', 'must be a child of $this' );
-               $elt->parent = null;
-               array_splice( $this->children, $idx, 1 );
-       }
-
-       /**
-        * Find $a in the list of children and insert $b before it.
-        * @param BalanceElement $a
-        * @param BalanceElement|string $b
-        */
-       public function insertBefore( BalanceElement $a, $b ) {
-               Assert::precondition(
-                       $this->parent !== 'flat', "Can't insertBefore after flattening."
-               );
-               $idx = array_search( $a, $this->children, true );
-               Assert::parameter( $idx !== false, '$a', 'must be a child of $this' );
-               if ( is_string( $b ) ) {
-                       array_splice( $this->children, $idx, 0, [ $b ] );
-               } else {
-                       Assert::parameter( $b->parent !== 'flat', '$b', "Can't be flat" );
-                       if ( $b->parent !== null ) {
-                               $b->parent->removeChild( $b );
-                       }
-                       array_splice( $this->children, $idx, 0, [ $b ] );
-                       $b->parent = $this;
-               }
-       }
-
-       /**
-        * Append $elt to the end of the list of children.
-        * @param BalanceElement|string $elt
-        */
-       public function appendChild( $elt ) {
-               Assert::precondition(
-                       $this->parent !== 'flat', "Can't appendChild after flattening."
-               );
-               if ( is_string( $elt ) ) {
-                       array_push( $this->children, $elt );
-                       return;
-               }
-               // Remove $elt from parent, if it had one.
-               if ( $elt->parent !== null ) {
-                       $elt->parent->removeChild( $elt );
-               }
-               array_push( $this->children, $elt );
-               $elt->parent = $this;
-       }
-
-       /**
-        * Transfer all of the children of $elt to $this.
-        * @param BalanceElement $elt
-        */
-       public function adoptChildren( BalanceElement $elt ) {
-               Assert::precondition(
-                       $elt->parent !== 'flat', "Can't adoptChildren after flattening."
-               );
-               foreach ( $elt->children as $child ) {
-                       if ( !is_string( $child ) ) {
-                               // This is an optimization which avoids an O(n^2) set of
-                               // array_splice operations.
-                               $child->parent = null;
-                       }
-                       $this->appendChild( $child );
-               }
-               $elt->children = [];
-       }
-
-       /**
-        * Flatten this node and all of its children into a string, as specified
-        * by the HTML serialization specification, and replace this node
-        * in its parent by that string.
-        *
-        * @param array $config Balancer configuration; see Balancer::__construct().
-        * @return string
-        *
-        * @see __toString()
-        */
-       public function flatten( array $config ) {
-               Assert::parameter( $this->parent !== null, '$this', 'must be a child' );
-               Assert::parameter( $this->parent !== 'flat', '$this', 'already flat' );
-               $idx = array_search( $this, $this->parent->children, true );
-               Assert::parameter(
-                       $idx !== false, '$this', 'must be a child of its parent'
-               );
-               $tidyCompat = $config['tidyCompat'];
-               if ( $tidyCompat ) {
-                       $blank = true;
-                       foreach ( $this->children as $elt ) {
-                               if ( !is_string( $elt ) ) {
-                                       $elt = $elt->flatten( $config );
-                               }
-                               if ( $blank && preg_match( '/[^\t\n\f\r ]/', $elt ) ) {
-                                       $blank = false;
-                               }
-                       }
-                       if ( $this->isHtmlNamed( 'mw:p-wrap' ) ) {
-                               $this->localName = 'p';
-                       } elseif ( $blank ) {
-                               // Add 'mw-empty-elt' class so elements can be hidden via CSS
-                               // for compatibility with legacy tidy.
-                               if ( !count( $this->attribs ) &&
-                                       ( $this->localName === 'tr' || $this->localName === 'li' )
-                               ) {
-                                       $this->attribs = [ 'class' => "mw-empty-elt" ];
-                               }
-                               $blank = false;
-                       } elseif (
-                               $this->isA( BalanceSets::$extraLinefeedSet ) &&
-                               count( $this->children ) > 0 &&
-                               substr( $this->children[0], 0, 1 ) == "\n"
-                       ) {
-                               // Double the linefeed after pre/listing/textarea
-                               // according to the (old) HTML5 fragment serialization
-                               // algorithm (see https://github.com/whatwg/html/issues/944)
-                               // to ensure this will round-trip.
-                               array_unshift( $this->children, "\n" );
-                       }
-                       $flat = $blank ? '' : "{$this}";
-               } else {
-                       $flat = "{$this}";
-               }
-               $this->parent->children[$idx] = $flat;
-               $this->parent = 'flat'; // for assertion checking
-               return $flat;
-       }
-
-       /**
-        * Serialize this node and all of its children to a string, as specified
-        * by the HTML serialization specification.
-        *
-        * @return string The serialization of the BalanceElement
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#serialising-html-fragments
-        */
-       public function __toString() {
-               $encAttribs = '';
-               foreach ( $this->attribs as $name => $value ) {
-                       $encValue = Sanitizer::encodeAttribute( $value );
-                       $encAttribs .= " $name=\"$encValue\"";
-               }
-               if ( !$this->isA( BalanceSets::$emptyElementSet ) ) {
-                       $out = "<{$this->localName}{$encAttribs}>";
-                       $len = strlen( $out );
-                       // flatten children
-                       foreach ( $this->children as $elt ) {
-                               $out .= "{$elt}";
-                       }
-                       $out .= "</{$this->localName}>";
-               } else {
-                       $out = "<{$this->localName}{$encAttribs} />";
-                       Assert::invariant(
-                               count( $this->children ) === 0,
-                               "Empty elements shouldn't have children."
-                       );
-               }
-               return $out;
-       }
-
-       // Utility functions on BalanceElements.
-
-       /**
-        * Determine if $this represents a specific HTML tag, is a member of
-        * a tag set, or is equal to another BalanceElement.
-        *
-        * @param BalanceElement|array|string $set The target BalanceElement,
-        *   set (from the BalanceSets class), or string (HTML tag name).
-        * @return bool
-        */
-       public function isA( $set ) {
-               if ( $set instanceof BalanceElement ) {
-                       return $this === $set;
-               } elseif ( is_array( $set ) ) {
-                       return isset( $set[$this->namespaceURI] ) &&
-                               isset( $set[$this->namespaceURI][$this->localName] );
-               } else {
-                       // assume this is an HTML element name.
-                       return $this->isHtml() && $this->localName === $set;
-               }
-       }
-
-       /**
-        * Determine if this element is an HTML element with the specified name
-        * @param string $tagName
-        * @return bool
-        */
-       public function isHtmlNamed( $tagName ) {
-               return $this->namespaceURI === BalanceSets::HTML_NAMESPACE
-                       && $this->localName === $tagName;
-       }
-
-       /**
-        * Determine if $this represents an element in the HTML namespace.
-        *
-        * @return bool
-        */
-       public function isHtml() {
-               return $this->namespaceURI === BalanceSets::HTML_NAMESPACE;
-       }
-
-       /**
-        * Determine if $this represents a MathML text integration point,
-        * as defined in the HTML5 specification.
-        *
-        * @return bool
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#mathml-text-integration-point
-        */
-       public function isMathmlTextIntegrationPoint() {
-               return $this->isA( BalanceSets::$mathmlTextIntegrationPointSet );
-       }
-
-       /**
-        * Determine if $this represents an HTML integration point,
-        * as defined in the HTML5 specification.
-        *
-        * @return bool
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point
-        */
-       public function isHtmlIntegrationPoint() {
-               if ( $this->isA( BalanceSets::$htmlIntegrationPointSet ) ) {
-                       return true;
-               }
-               if (
-                       $this->namespaceURI === BalanceSets::MATHML_NAMESPACE &&
-                       $this->localName === 'annotation-xml' &&
-                       isset( $this->attribs['encoding'] ) &&
-                       ( strcasecmp( $this->attribs['encoding'], 'text/html' ) == 0 ||
-                       strcasecmp( $this->attribs['encoding'], 'application/xhtml+xml' ) == 0 )
-               ) {
-                       return true;
-               }
-               return false;
-       }
-
-       /**
-        * Get a string key for the Noah's Ark algorithm
-        * @return string
-        */
-       public function getNoahKey() {
-               if ( $this->noahKey === null ) {
-                       $attribs = $this->attribs;
-                       ksort( $attribs );
-                       $this->noahKey = serialize( [ $this->namespaceURI, $this->localName, $attribs ] );
-               }
-               return $this->noahKey;
-       }
-}
-
-/**
- * The "stack of open elements" as defined in the HTML5 tree builder
- * spec.  This contains methods to ensure that content (start tags, text)
- * are inserted at the correct place in the output string, and to
- * flatten BalanceElements are they are closed to avoid holding onto
- * a complete DOM tree for the document in memory.
- *
- * The stack defines a PHP iterator to traverse it in "reverse order",
- * that is, the most-recently-added element is visited first in a
- * foreach loop.
- *
- * @ingroup Parser
- * @since 1.27
- * @see https://html.spec.whatwg.org/multipage/syntax.html#the-stack-of-open-elements
- */
-class BalanceStack implements IteratorAggregate {
-       /**
-        * Backing storage for the stack.
-        * @var BalanceElement[] $elements
-        */
-       private $elements = [];
-       /**
-        * Foster parent mode determines how nodes are inserted into the
-        * stack.
-        * @var bool $fosterParentMode
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#foster-parent
-        */
-       public $fosterParentMode = false;
-       /**
-        * Configuration options governing flattening.
-        * @var array $config
-        * @see Balancer::__construct()
-        */
-       private $config;
-       /**
-        * Reference to the current element
-        */
-       public $currentNode;
-
-       /**
-        * Create a new BalanceStack with a single BalanceElement on it,
-        * representing the root &lt;html&gt; node.
-        * @param array $config Balancer configuration; see Balancer::_construct().
-        */
-       public function __construct( array $config ) {
-               // always a root <html> element on the stack
-               array_push(
-                       $this->elements,
-                       new BalanceElement( BalanceSets::HTML_NAMESPACE, 'html', [] )
-               );
-               $this->currentNode = $this->elements[0];
-               $this->config = $config;
-       }
-
-       /**
-        * Return a string representing the output of the tree builder:
-        * all the children of the root &lt;html&gt; node.
-        * @return string
-        */
-       public function getOutput() {
-               // Don't include the outer '<html>....</html>'
-               $out = '';
-               foreach ( $this->elements[0]->children as $elt ) {
-                       $out .= is_string( $elt ) ? $elt :
-                               $elt->flatten( $this->config );
-               }
-               return $out;
-       }
-
-       /**
-        * Insert a comment at the appropriate place for inserting a node.
-        * @param string $value Content of the comment.
-        * @return string
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#insert-a-comment
-        */
-       public function insertComment( $value ) {
-               // Just another type of text node, except for tidy p-wrapping.
-               return $this->insertText( '<!--' . $value . '-->', true );
-       }
-
-       /**
-        * Insert text at the appropriate place for inserting a node.
-        * @param string $value
-        * @param bool $isComment
-        * @return string
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#appropriate-place-for-inserting-a-node
-        */
-       public function insertText( $value, $isComment = false ) {
-               if (
-                       $this->fosterParentMode &&
-                       $this->currentNode->isA( BalanceSets::$tableSectionRowSet )
-               ) {
-                       $this->fosterParent( $value );
-               } elseif (
-                       $this->config['tidyCompat'] && !$isComment &&
-                       $this->currentNode->isA( BalanceSets::$tidyPWrapSet )
-               ) {
-                       $this->insertHTMLElement( 'mw:p-wrap', [] );
-                       return $this->insertText( $value );
-               } else {
-                       $this->currentNode->appendChild( $value );
-               }
-       }
-
-       /**
-        * Insert a BalanceElement at the appropriate place, pushing it
-        * on to the open elements stack.
-        * @param string $namespaceURI The element namespace
-        * @param string $tag The tag name
-        * @param string $attribs Normalized attributes, as a string.
-        * @return BalanceElement
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#insert-a-foreign-element
-        */
-       public function insertForeignElement( $namespaceURI, $tag, $attribs ) {
-               return $this->insertElement(
-                       new BalanceElement( $namespaceURI, $tag, $attribs )
-               );
-       }
-
-       /**
-        * Insert an HTML element at the appropriate place, pushing it on to
-        * the open elements stack.
-        * @param string $tag The tag name
-        * @param string $attribs Normalized attributes, as a string.
-        * @return BalanceElement
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#insert-an-html-element
-        */
-       public function insertHTMLElement( $tag, $attribs ) {
-               return $this->insertForeignElement(
-                       BalanceSets::HTML_NAMESPACE, $tag, $attribs
-               );
-       }
-
-       /**
-        * Insert an element at the appropriate place and push it on to the
-        * open elements stack.
-        * @param BalanceElement $elt
-        * @return BalanceElement
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#appropriate-place-for-inserting-a-node
-        */
-       public function insertElement( BalanceElement $elt ) {
-               if (
-                       $this->currentNode->isHtmlNamed( 'mw:p-wrap' ) &&
-                       !$elt->isA( BalanceSets::$tidyInlineSet )
-               ) {
-                       // Tidy compatibility.
-                       $this->pop();
-               }
-               if (
-                       $this->fosterParentMode &&
-                       $this->currentNode->isA( BalanceSets::$tableSectionRowSet )
-               ) {
-                       $elt = $this->fosterParent( $elt );
-               } else {
-                       $this->currentNode->appendChild( $elt );
-               }
-               Assert::invariant( $elt->parent !== null, "$elt must be in tree" );
-               Assert::invariant( $elt->parent !== 'flat', "$elt must not have been previous flattened" );
-               array_push( $this->elements, $elt );
-               $this->currentNode = $elt;
-               return $elt;
-       }
-
-       /**
-        * Determine if the stack has $tag in scope.
-        * @param BalanceElement|array|string $tag
-        * @return bool
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope
-        */
-       public function inScope( $tag ) {
-               return $this->inSpecificScope( $tag, BalanceSets::$inScopeSet );
-       }
-
-       /**
-        * Determine if the stack has $tag in button scope.
-        * @param BalanceElement|array|string $tag
-        * @return bool
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-button-scope
-        */
-       public function inButtonScope( $tag ) {
-               return $this->inSpecificScope( $tag, BalanceSets::inButtonScopeSet() );
-       }
-
-       /**
-        * Determine if the stack has $tag in list item scope.
-        * @param BalanceElement|array|string $tag
-        * @return bool
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-list-item-scope
-        */
-       public function inListItemScope( $tag ) {
-               return $this->inSpecificScope( $tag, BalanceSets::inListItemScopeSet() );
-       }
-
-       /**
-        * Determine if the stack has $tag in table scope.
-        * @param BalanceElement|array|string $tag
-        * @return bool
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-table-scope
-        */
-       public function inTableScope( $tag ) {
-               return $this->inSpecificScope( $tag, BalanceSets::$inTableScopeSet );
-       }
-
-       /**
-        * Determine if the stack has $tag in select scope.
-        * @param BalanceElement|array|string $tag
-        * @return bool
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-select-scope
-        */
-       public function inSelectScope( $tag ) {
-               // Can't use inSpecificScope to implement this, since it involves
-               // *inverting* a set of tags.  Implement manually.
-               foreach ( $this as $elt ) {
-                       if ( $elt->isA( $tag ) ) {
-                               return true;
-                       }
-                       if ( !$elt->isA( BalanceSets::$inInvertedSelectScopeSet ) ) {
-                               return false;
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Determine if the stack has $tag in a specific scope, $set.
-        * @param BalanceElement|array|string $tag
-        * @param BalanceElement|array|string $set
-        * @return bool
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-the-specific-scope
-        */
-       public function inSpecificScope( $tag, $set ) {
-               foreach ( $this as $elt ) {
-                       if ( $elt->isA( $tag ) ) {
-                               return true;
-                       }
-                       if ( $elt->isA( $set ) ) {
-                               return false;
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Generate implied end tags.
-        * @param string $butnot
-        * @param bool $thorough True if we should generate end tags thoroughly.
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags
-        */
-       public function generateImpliedEndTags( $butnot = null, $thorough = false ) {
-               $endTagSet = $thorough ?
-                       BalanceSets::$thoroughImpliedEndTagsSet :
-                       BalanceSets::$impliedEndTagsSet;
-               while ( $this->currentNode ) {
-                       if ( $butnot !== null && $this->currentNode->isHtmlNamed( $butnot ) ) {
-                               break;
-                       }
-                       if ( !$this->currentNode->isA( $endTagSet ) ) {
-                               break;
-                       }
-                       $this->pop();
-               }
-       }
-
-       /**
-        * Return the adjusted current node.
-        * @param string $fragmentContext
-        * @return string
-        */
-       public function adjustedCurrentNode( $fragmentContext ) {
-               return ( $fragmentContext && count( $this->elements ) === 1 ) ?
-                       $fragmentContext : $this->currentNode;
-       }
-
-       /**
-        * Return an iterator over this stack which visits the current node
-        * first, and the root node last.
-        * @return \Iterator
-        */
-       public function getIterator() {
-               return new ReverseArrayIterator( $this->elements );
-       }
-
-       /**
-        * Return the BalanceElement at the given position $idx, where
-        * position 0 represents the root element.
-        * @param int $idx
-        * @return BalanceElement
-        */
-       public function node( $idx ) {
-               return $this->elements[ $idx ];
-       }
-
-       /**
-        * Replace the element at position $idx in the BalanceStack with $elt.
-        * @param int $idx
-        * @param BalanceElement $elt
-        */
-       public function replaceAt( $idx, BalanceElement $elt ) {
-               Assert::precondition(
-                       $this->elements[$idx]->parent !== 'flat',
-                       'Replaced element should not have already been flattened.'
-               );
-               Assert::precondition(
-                       $elt->parent !== 'flat',
-                       'New element should not have already been flattened.'
-               );
-               $this->elements[$idx] = $elt;
-               if ( $idx === count( $this->elements ) - 1 ) {
-                       $this->currentNode = $elt;
-               }
-       }
-
-       /**
-        * Return the position of the given BalanceElement, set, or
-        * HTML tag name string in the BalanceStack.
-        * @param BalanceElement|array|string $tag
-        * @return int
-        */
-       public function indexOf( $tag ) {
-               for ( $i = count( $this->elements ) - 1; $i >= 0; $i-- ) {
-                       if ( $this->elements[$i]->isA( $tag ) ) {
-                               return $i;
-                       }
-               }
-               return -1;
-       }
-
-       /**
-        * Return the number of elements currently in the BalanceStack.
-        * @return int
-        */
-       public function length() {
-               return count( $this->elements );
-       }
-
-       /**
-        * Remove the current node from the BalanceStack, flattening it
-        * in the process.
-        */
-       public function pop() {
-               $elt = array_pop( $this->elements );
-               if ( count( $this->elements ) ) {
-                       $this->currentNode = $this->elements[ count( $this->elements ) - 1 ];
-               } else {
-                       $this->currentNode = null;
-               }
-               if ( !$elt->isHtmlNamed( 'mw:p-wrap' ) ) {
-                       $elt->flatten( $this->config );
-               }
-       }
-
-       /**
-        * Remove all nodes up to and including position $idx from the
-        * BalanceStack, flattening them in the process.
-        * @param int $idx
-        */
-       public function popTo( $idx ) {
-               for ( $length = count( $this->elements ); $length > $idx; $length-- ) {
-                       $this->pop();
-               }
-       }
-
-       /**
-        * Pop elements off the stack up to and including the first
-        * element with the specified HTML tagname (or matching the given
-        * set).
-        * @param BalanceElement|array|string $tag
-        */
-       public function popTag( $tag ) {
-               while ( $this->currentNode ) {
-                       if ( $this->currentNode->isA( $tag ) ) {
-                               $this->pop();
-                               break;
-                       }
-                       $this->pop();
-               }
-       }
-
-       /**
-        * Pop elements off the stack *not including* the first element
-        * in the specified set.
-        * @param BalanceElement|array|string $set
-        */
-       public function clearToContext( $set ) {
-               // Note that we don't loop to 0. Never pop the <html> elt off.
-               for ( $length = count( $this->elements ); $length > 1; $length-- ) {
-                       if ( $this->currentNode->isA( $set ) ) {
-                               break;
-                       }
-                       $this->pop();
-               }
-       }
-
-       /**
-        * Remove the given $elt from the BalanceStack, optionally
-        * flattening it in the process.
-        * @param BalanceElement $elt The element to remove.
-        * @param bool $flatten Whether to flatten the removed element.
-        */
-       public function removeElement( BalanceElement $elt, $flatten = true ) {
-               Assert::parameter(
-                       $elt->parent !== 'flat',
-                       '$elt',
-                       '$elt should not already have been flattened.'
-               );
-               Assert::parameter(
-                       $elt->parent->parent !== 'flat',
-                       '$elt',
-                       'The parent of $elt should not already have been flattened.'
-               );
-               $idx = array_search( $elt, $this->elements, true );
-               Assert::parameter( $idx !== false, '$elt', 'must be in stack' );
-               array_splice( $this->elements, $idx, 1 );
-               if ( $idx === count( $this->elements ) ) {
-                       $this->currentNode = $this->elements[$idx - 1];
-               }
-               if ( $flatten ) {
-                       // serialize $elt into its parent
-                       // otherwise, it will eventually serialize when the parent
-                       // is serialized, we just hold onto the memory for its
-                       // tree of objects a little longer.
-                       $elt->flatten( $this->config );
-               }
-               Assert::postcondition(
-                       array_search( $elt, $this->elements, true ) === false,
-                       '$elt should no longer be in open elements stack'
-               );
-       }
-
-       /**
-        * Find $a in the BalanceStack and insert $b after it.
-        * @param BalanceElement $a
-        * @param BalanceElement $b
-        */
-       public function insertAfter( BalanceElement $a, BalanceElement $b ) {
-               $idx = $this->indexOf( $a );
-               Assert::parameter( $idx !== false, '$a', 'must be in stack' );
-               if ( $idx === count( $this->elements ) - 1 ) {
-                       array_push( $this->elements, $b );
-                       $this->currentNode = $b;
-               } else {
-                       array_splice( $this->elements, $idx + 1, 0, [ $b ] );
-               }
-       }
-
-       // Fostering and adoption.
-
-       /**
-        * Foster parent the given $elt in the stack of open elements.
-        * @param BalanceElement|string $elt
-        * @return BalanceElement|string
-        *
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#foster-parent
-        */
-       private function fosterParent( $elt ) {
-               $lastTable = $this->indexOf( 'table' );
-               $lastTemplate = $this->indexOf( 'template' );
-               $parent = null;
-               $before = null;
-
-               if ( $lastTemplate >= 0 && ( $lastTable < 0 || $lastTemplate > $lastTable ) ) {
-                       $parent = $this->elements[$lastTemplate];
-               } elseif ( $lastTable >= 0 ) {
-                       $parent = $this->elements[$lastTable]->parent;
-                       // Assume all tables have parents, since we're not running scripts!
-                       Assert::invariant(
-                               $parent !== null, "All tables should have parents"
-                       );
-                       $before = $this->elements[$lastTable];
-               } else {
-                       $parent = $this->elements[0]; // the `html` element.
-               }
-
-               if ( $this->config['tidyCompat'] ) {
-                       if ( is_string( $elt ) ) {
-                               // We're fostering text: do we need a p-wrapper?
-                               if ( $parent->isA( BalanceSets::$tidyPWrapSet ) ) {
-                                       $this->insertHTMLElement( 'mw:p-wrap', [] );
-                                       $this->insertText( $elt );
-                                       return $elt;
-                               }
-                       } else {
-                               // We're fostering an element; do we need to merge p-wrappers?
-                               if ( $elt->isHtmlNamed( 'mw:p-wrap' ) ) {
-                                       $idx = $before ?
-                                               array_search( $before, $parent->children, true ) :
-                                               count( $parent->children );
-                                       $after = $idx > 0 ? $parent->children[$idx - 1] : '';
-                                       if (
-                                               $after instanceof BalanceElement &&
-                                               $after->isHtmlNamed( 'mw:p-wrap' )
-                                       ) {
-                                               return $after; // Re-use existing p-wrapper.
-                                       }
-                               }
-                       }
-               }
-
-               if ( $before ) {
-                       $parent->insertBefore( $before, $elt );
-               } else {
-                       $parent->appendChild( $elt );
-               }
-               return $elt;
-       }
-
-       /**
-        * Run the "adoption agency algoritm" (AAA) for the given subject
-        * tag name.
-        * @param string $tag The subject tag name.
-        * @param BalanceActiveFormattingElements $afe The current
-        *   active formatting elements list.
-        * @return true if the adoption agency algorithm "did something", false
-        *   if more processing is required by the caller.
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#adoption-agency-algorithm
-        */
-       public function adoptionAgency( $tag, $afe ) {
-               // If the current node is an HTML element whose tag name is subject,
-               // and the current node is not in the list of active formatting
-               // elements, then pop the current node off the stack of open
-               // elements and abort these steps.
-               if (
-                       $this->currentNode->isHtmlNamed( $tag ) &&
-                       !$afe->isInList( $this->currentNode )
-               ) {
-                       $this->pop();
-                       return true; // no more handling required
-               }
-
-               // Outer loop: If outer loop counter is greater than or
-               // equal to eight, then abort these steps.
-               for ( $outer = 0; $outer < 8; $outer++ ) {
-                       // Let the formatting element be the last element in the list
-                       // of active formatting elements that: is between the end of
-                       // the list and the last scope marker in the list, if any, or
-                       // the start of the list otherwise, and has the same tag name
-                       // as the token.
-                       $fmtElt = $afe->findElementByTag( $tag );
-
-                       // If there is no such node, then abort these steps and instead
-                       // act as described in the "any other end tag" entry below.
-                       if ( !$fmtElt ) {
-                               return false; // false means handle by the default case
-                       }
-
-                       // Otherwise, if there is such a node, but that node is not in
-                       // the stack of open elements, then this is a parse error;
-                       // remove the element from the list, and abort these steps.
-                       $index = $this->indexOf( $fmtElt );
-                       if ( $index < 0 ) {
-                               $afe->remove( $fmtElt );
-                               return true;   // true means no more handling required
-                       }
-
-                       // Otherwise, if there is such a node, and that node is also in
-                       // the stack of open elements, but the element is not in scope,
-                       // then this is a parse error; ignore the token, and abort
-                       // these steps.
-                       if ( !$this->inScope( $fmtElt ) ) {
-                               return true;
-                       }
-
-                       // Let the furthest block be the topmost node in the stack of
-                       // open elements that is lower in the stack than the formatting
-                       // element, and is an element in the special category. There
-                       // might not be one.
-                       $furthestBlock = null;
-                       $furthestBlockIndex = -1;
-                       $stackLength = $this->length();
-                       for ( $i = $index + 1; $i < $stackLength; $i++ ) {
-                               if ( $this->node( $i )->isA( BalanceSets::$specialSet ) ) {
-                                       $furthestBlock = $this->node( $i );
-                                       $furthestBlockIndex = $i;
-                                       break;
-                               }
-                       }
-
-                       // If there is no furthest block, then the UA must skip the
-                       // subsequent steps and instead just pop all the nodes from the
-                       // bottom of the stack of open elements, from the current node
-                       // up to and including the formatting element, and remove the
-                       // formatting element from the list of active formatting
-                       // elements.
-                       if ( !$furthestBlock ) {
-                               $this->popTag( $fmtElt );
-                               $afe->remove( $fmtElt );
-                               return true;
-                       }
-
-                       // Let the common ancestor be the element immediately above
-                       // the formatting element in the stack of open elements.
-                       $ancestor = $this->node( $index - 1 );
-
-                       // Let a bookmark note the position of the formatting
-                       // element in the list of active formatting elements
-                       // relative to the elements on either side of it in the
-                       // list.
-                       $BOOKMARK = new BalanceElement( '[bookmark]', '[bookmark]', [] );
-                       $afe->insertAfter( $fmtElt, $BOOKMARK );
-
-                       // Let node and last node be the furthest block.
-                       $node = $furthestBlock;
-                       $lastNode = $furthestBlock;
-                       $nodeIndex = $furthestBlockIndex;
-                       $isAFE = false;
-
-                       // Inner loop
-                       for ( $inner = 1; true; $inner++ ) {
-                               // Let node be the element immediately above node in
-                               // the stack of open elements, or if node is no longer
-                               // in the stack of open elements (e.g. because it got
-                               // removed by this algorithm), the element that was
-                               // immediately above node in the stack of open elements
-                               // before node was removed.
-                               $node = $this->node( --$nodeIndex );
-
-                               // If node is the formatting element, then go
-                               // to the next step in the overall algorithm.
-                               if ( $node === $fmtElt ) break;
-
-                               // If the inner loop counter is greater than three and node
-                               // is in the list of active formatting elements, then remove
-                               // node from the list of active formatting elements.
-                               $isAFE = $afe->isInList( $node );
-                               if ( $inner > 3 && $isAFE ) {
-                                       $afe->remove( $node );
-                                       $isAFE = false;
-                               }
-
-                               // If node is not in the list of active formatting
-                               // elements, then remove node from the stack of open
-                               // elements and then go back to the step labeled inner
-                               // loop.
-                               if ( !$isAFE ) {
-                                       // Don't flatten here, since we're about to relocate
-                                       // parts of this $node.
-                                       $this->removeElement( $node, false );
-                                       continue;
-                               }
-
-                               // Create an element for the token for which the
-                               // element node was created with common ancestor as
-                               // the intended parent, replace the entry for node
-                               // in the list of active formatting elements with an
-                               // entry for the new element, replace the entry for
-                               // node in the stack of open elements with an entry for
-                               // the new element, and let node be the new element.
-                               $newElt = new BalanceElement(
-                                       $node->namespaceURI, $node->localName, $node->attribs );
-                               $afe->replace( $node, $newElt );
-                               $this->replaceAt( $nodeIndex, $newElt );
-                               $node = $newElt;
-
-                               // If last node is the furthest block, then move the
-                               // aforementioned bookmark to be immediately after the
-                               // new node in the list of active formatting elements.
-                               if ( $lastNode === $furthestBlock ) {
-                                       $afe->remove( $BOOKMARK );
-                                       $afe->insertAfter( $newElt, $BOOKMARK );
-                               }
-
-                               // Insert last node into node, first removing it from
-                               // its previous parent node if any.
-                               $node->appendChild( $lastNode );
-
-                               // Let last node be node.
-                               $lastNode = $node;
-                       }
-
-                       // If the common ancestor node is a table, tbody, tfoot,
-                       // thead, or tr element, then, foster parent whatever last
-                       // node ended up being in the previous step, first removing
-                       // it from its previous parent node if any.
-                       if (
-                               $this->fosterParentMode &&
-                               $ancestor->isA( BalanceSets::$tableSectionRowSet )
-                       ) {
-                               $this->fosterParent( $lastNode );
-                       } else {
-                               // Otherwise, append whatever last node ended up being in
-                               // the previous step to the common ancestor node, first
-                               // removing it from its previous parent node if any.
-                               $ancestor->appendChild( $lastNode );
-                       }
-
-                       // Create an element for the token for which the
-                       // formatting element was created, with furthest block
-                       // as the intended parent.
-                       $newElt2 = new BalanceElement(
-                               $fmtElt->namespaceURI, $fmtElt->localName, $fmtElt->attribs );
-
-                       // Take all of the child nodes of the furthest block and
-                       // append them to the element created in the last step.
-                       $newElt2->adoptChildren( $furthestBlock );
-
-                       // Append that new element to the furthest block.
-                       $furthestBlock->appendChild( $newElt2 );
-
-                       // Remove the formatting element from the list of active
-                       // formatting elements, and insert the new element into the
-                       // list of active formatting elements at the position of
-                       // the aforementioned bookmark.
-                       $afe->remove( $fmtElt );
-                       $afe->replace( $BOOKMARK, $newElt2 );
-
-                       // Remove the formatting element from the stack of open
-                       // elements, and insert the new element into the stack of
-                       // open elements immediately below the position of the
-                       // furthest block in that stack.
-                       $this->removeElement( $fmtElt );
-                       $this->insertAfter( $furthestBlock, $newElt2 );
-               }
-
-               return true;
-       }
-
-       /**
-        * Return the contents of the open elements stack as a string for
-        * debugging.
-        * @return string
-        */
-       public function __toString() {
-               $r = [];
-               foreach ( $this->elements as $elt ) {
-                       array_push( $r, $elt->localName );
-               }
-               return implode( ' ', $r );
-       }
-}
-
-/**
- * A pseudo-element used as a marker in the list of active formatting elements
- *
- * @ingroup Parser
- * @since 1.27
- */
-class BalanceMarker {
-       public $nextAFE;
-       public $prevAFE;
-}
-
-/**
- * The list of active formatting elements, which is used to handle
- * mis-nested formatting element tags in the HTML5 tree builder
- * specification.
- *
- * @ingroup Parser
- * @since 1.27
- * @see https://html.spec.whatwg.org/multipage/syntax.html#list-of-active-formatting-elements
- */
-class BalanceActiveFormattingElements {
-       /** The last (most recent) element in the list */
-       private $tail;
-
-       /** The first (least recent) element in the list */
-       private $head;
-
-       /**
-        * An array of arrays representing the population of elements in each bucket
-        * according to the Noah's Ark clause. The outer array is stack-like, with each
-        * integer-indexed element representing a segment of the list, bounded by
-        * markers. The first element represents the segment of the list before the
-        * first marker.
-        *
-        * The inner arrays are indexed by "Noah key", which is a string which uniquely
-        * identifies each bucket according to the rules in the spec. The value in
-        * the inner array is the first (least recently inserted) element in the bucket,
-        * and subsequent members of the bucket can be found by iterating through the
-        * singly-linked list via $node->nextNoah.
-        *
-        * This is optimised for the most common case of inserting into a bucket
-        * with zero members, and deleting a bucket containing one member. In the
-        * worst case, iteration through the list is still O(1) in the document
-        * size, since each bucket can have at most 3 members.
-        */
-       private $noahTableStack = [ [] ];
-
-       public function __destruct() {
-               $next = null;
-               for ( $node = $this->head; $node; $node = $next ) {
-                       $next = $node->nextAFE;
-                       $node->prevAFE = $node->nextAFE = $node->nextNoah = null;
-               }
-               $this->head = $this->tail = $this->noahTableStack = null;
-       }
-
-       public function insertMarker() {
-               $elt = new BalanceMarker;
-               if ( $this->tail ) {
-                       $this->tail->nextAFE = $elt;
-                       $elt->prevAFE = $this->tail;
-               } else {
-                       $this->head = $elt;
-               }
-               $this->tail = $elt;
-               $this->noahTableStack[] = [];
-       }
-
-       /**
-        * Follow the steps required when the spec requires us to "push onto the
-        * list of active formatting elements".
-        * @param BalanceElement $elt
-        */
-       public function push( BalanceElement $elt ) {
-               // Must not be in the list already
-               if ( $elt->prevAFE !== null || $this->head === $elt ) {
-                       throw new ParameterAssertionException( '$elt',
-                               'Cannot insert a node into the AFE list twice' );
-               }
-
-               // "Noah's Ark clause" -- if there are already three copies of
-               // this element before we encounter a marker, then drop the last
-               // one.
-               $noahKey = $elt->getNoahKey();
-               $table =& $this->noahTableStack[ count( $this->noahTableStack ) - 1 ];
-               if ( !isset( $table[$noahKey] ) ) {
-                       $table[$noahKey] = $elt;
-               } else {
-                       $count = 1;
-                       $head = $tail = $table[$noahKey];
-                       while ( $tail->nextNoah ) {
-                               $tail = $tail->nextNoah;
-                               $count++;
-                       }
-                       if ( $count >= 3 ) {
-                               $this->remove( $head );
-                       }
-                       $tail->nextNoah = $elt;
-               }
-               // Add to the main AFE list
-               if ( $this->tail ) {
-                       $this->tail->nextAFE = $elt;
-                       $elt->prevAFE = $this->tail;
-               } else {
-                       $this->head = $elt;
-               }
-               $this->tail = $elt;
-       }
-
-       /**
-        * Follow the steps required when the spec asks us to "clear the list of
-        * active formatting elements up to the last marker".
-        */
-       public function clearToMarker() {
-               // Iterate back through the list starting from the tail
-               $tail = $this->tail;
-               while ( $tail && !( $tail instanceof BalanceMarker ) ) {
-                       // Unlink the element
-                       $prev = $tail->prevAFE;
-                       $tail->prevAFE = null;
-                       if ( $prev ) {
-                               $prev->nextAFE = null;
-                       }
-                       $tail->nextNoah = null;
-                       $tail = $prev;
-               }
-               // If we finished on a marker, unlink it and pop it off the Noah table stack
-               if ( $tail ) {
-                       $prev = $tail->prevAFE;
-                       if ( $prev ) {
-                               $prev->nextAFE = null;
-                       }
-                       $tail = $prev;
-                       array_pop( $this->noahTableStack );
-               } else {
-                       // No marker: wipe the top-level Noah table (which is the only one)
-                       $this->noahTableStack[0] = [];
-               }
-               // If we removed all the elements, clear the head pointer
-               if ( !$tail ) {
-                       $this->head = null;
-               }
-               $this->tail = $tail;
-       }
-
-       /**
-        * Find and return the last element with the specified tag between the
-        * end of the list and the last marker on the list.
-        * Used when parsing &lt;a&gt; "in body mode".
-        * @param string $tag
-        * @return null|Node
-        */
-       public function findElementByTag( $tag ) {
-               $elt = $this->tail;
-               while ( $elt && !( $elt instanceof BalanceMarker ) ) {
-                       if ( $elt->localName === $tag ) {
-                               return $elt;
-                       }
-                       $elt = $elt->prevAFE;
-               }
-               return null;
-       }
-
-       /**
-        * Determine whether an element is in the list of formatting elements.
-        * @param BalanceElement $elt
-        * @return bool
-        */
-       public function isInList( BalanceElement $elt ) {
-               return $this->head === $elt || $elt->prevAFE;
-       }
-
-       /**
-        * Find the element $elt in the list and remove it.
-        * Used when parsing &lt;a&gt; in body mode.
-        *
-        * @param BalanceElement $elt
-        */
-       public function remove( BalanceElement $elt ) {
-               if ( $this->head !== $elt && !$elt->prevAFE ) {
-                       throw new ParameterAssertionException( '$elt',
-                               "Attempted to remove an element which is not in the AFE list" );
-               }
-               // Update head and tail pointers
-               if ( $this->head === $elt ) {
-                       $this->head = $elt->nextAFE;
-               }
-               if ( $this->tail === $elt ) {
-                       $this->tail = $elt->prevAFE;
-               }
-               // Update previous element
-               if ( $elt->prevAFE ) {
-                       $elt->prevAFE->nextAFE = $elt->nextAFE;
-               }
-               // Update next element
-               if ( $elt->nextAFE ) {
-                       $elt->nextAFE->prevAFE = $elt->prevAFE;
-               }
-               // Clear pointers so that isInList() etc. will work
-               $elt->prevAFE = $elt->nextAFE = null;
-               // Update Noah list
-               $this->removeFromNoahList( $elt );
-       }
-
-       private function addToNoahList( BalanceElement $elt ) {
-               $noahKey = $elt->getNoahKey();
-               $table =& $this->noahTableStack[ count( $this->noahTableStack ) - 1 ];
-               if ( !isset( $table[$noahKey] ) ) {
-                       $table[$noahKey] = $elt;
-               } else {
-                       $tail = $table[$noahKey];
-                       while ( $tail->nextNoah ) {
-                               $tail = $tail->nextNoah;
-                       }
-                       $tail->nextNoah = $elt;
-               }
-       }
-
-       private function removeFromNoahList( BalanceElement $elt ) {
-               $table =& $this->noahTableStack[ count( $this->noahTableStack ) - 1 ];
-               $key = $elt->getNoahKey();
-               $noahElt = $table[$key];
-               if ( $noahElt === $elt ) {
-                       if ( $noahElt->nextNoah ) {
-                               $table[$key] = $noahElt->nextNoah;
-                               $noahElt->nextNoah = null;
-                       } else {
-                               unset( $table[$key] );
-                       }
-               } else {
-                       do {
-                               $prevNoahElt = $noahElt;
-                               $noahElt = $prevNoahElt->nextNoah;
-                               if ( $noahElt === $elt ) {
-                                       // Found it, unlink
-                                       $prevNoahElt->nextNoah = $elt->nextNoah;
-                                       $elt->nextNoah = null;
-                                       break;
-                               }
-                       } while ( $noahElt );
-               }
-       }
-
-       /**
-        * Find element $a in the list and replace it with element $b
-        *
-        * @param BalanceElement $a
-        * @param BalanceElement $b
-        */
-       public function replace( BalanceElement $a, BalanceElement $b ) {
-               if ( $this->head !== $a && !$a->prevAFE ) {
-                       throw new ParameterAssertionException( '$a',
-                               "Attempted to replace an element which is not in the AFE list" );
-               }
-               // Update head and tail pointers
-               if ( $this->head === $a ) {
-                       $this->head = $b;
-               }
-               if ( $this->tail === $a ) {
-                       $this->tail = $b;
-               }
-               // Update previous element
-               if ( $a->prevAFE ) {
-                       $a->prevAFE->nextAFE = $b;
-               }
-               // Update next element
-               if ( $a->nextAFE ) {
-                       $a->nextAFE->prevAFE = $b;
-               }
-               $b->prevAFE = $a->prevAFE;
-               $b->nextAFE = $a->nextAFE;
-               $a->nextAFE = $a->prevAFE = null;
-               // Update Noah list
-               $this->removeFromNoahList( $a );
-               $this->addToNoahList( $b );
-       }
-
-       /**
-        * Find $a in the list and insert $b after it.
-
-        * @param BalanceElement $a
-        * @param BalanceElement $b
-        */
-       public function insertAfter( BalanceElement $a, BalanceElement $b ) {
-               if ( $this->head !== $a && !$a->prevAFE ) {
-                       throw new ParameterAssertionException( '$a',
-                               "Attempted to insert after an element which is not in the AFE list" );
-               }
-               if ( $this->tail === $a ) {
-                       $this->tail = $b;
-               }
-               if ( $a->nextAFE ) {
-                       $a->nextAFE->prevAFE = $b;
-               }
-               $b->nextAFE = $a->nextAFE;
-               $b->prevAFE = $a;
-               $a->nextAFE = $b;
-               $this->addToNoahList( $b );
-       }
-
-       /**
-        * Reconstruct the active formatting elements.
-        * @param BalanceStack $stack The open elements stack
-        * @see https://html.spec.whatwg.org/multipage/syntax.html#reconstruct-the-active-formatting-elements
-        */
-       public function reconstruct( $stack ) {
-               $entry = $this->tail;
-               // If there are no entries in the list of active formatting elements,
-               // then there is nothing to reconstruct
-               if ( !$entry ) {
-                       return;
-               }
-               // If the last is a marker, do nothing.
-               if ( $entry instanceof BalanceMarker ) {
-                       return;
-               }
-               // Or if it is an open element, do nothing.
-               if ( $stack->indexOf( $entry ) >= 0 ) {
-                       return;
-               }
-
-               // Loop backward through the list until we find a marker or an
-               // open element
-               $foundIt = false;
-               while ( $entry->prevAFE ) {
-                       $entry = $entry->prevAFE;
-                       if ( $entry instanceof BalanceMarker || $stack->indexOf( $entry ) >= 0 ) {
-                               $foundIt = true;
-                               break;
-                       }
-               }
-
-               // Now loop forward, starting from the element after the current one (or
-               // the first element if we didn't find a marker or open element),
-               // recreating formatting elements and pushing them back onto the list
-               // of open elements.
-               if ( $foundIt ) {
-                       $entry = $entry->nextAFE;
-               }
-               do {
-                       $newElement = $stack->insertHTMLElement(
-                               $entry->localName,
-                               $entry->attribs );
-                       $this->replace( $entry, $newElement );
-                       $entry = $newElement->nextAFE;
-               } while ( $entry );
-       }
-
-       /**
-        * Get a string representation of the AFE list, for debugging
-        */
-       public function __toString() {
-               $prev = null;
-               $s = '';
-               for ( $node = $this->head; $node; $prev = $node, $node = $node->nextAFE ) {
-                       if ( $node instanceof BalanceMarker ) {
-                               $s .= "MARKER\n";
-                               continue;
-                       }
-                       $s .= $node->localName . '#' . substr( md5( spl_object_hash( $node ) ), 0, 8 );
-                       if ( $node->nextNoah ) {
-                               $s .= " (noah sibling: {$node->nextNoah->localName}#" .
-                                       substr( md5( spl_object_hash( $node->nextNoah ) ), 0, 8 ) .
-                                       ')';
-                       }
-                       if ( $node->nextAFE && $node->nextAFE->prevAFE !== $node ) {
-                               $s .= " (reverse link is wrong!)";
-                       }
-                       $s .= "\n";
-               }
-               if ( $prev !== $this->tail ) {
-                       $s .= "(tail pointer is wrong!)\n";
-               }
-               return $s;
-       }
-}
-
-/**
- * An implementation of the tree building portion of the HTML5 parsing
- * spec.
- *
- * This is used to balance and tidy output so that the result can
- * always be cleanly serialized/deserialized by an HTML5 parser.  It
- * does *not* guarantee "conforming" output -- the HTML5 spec contains
- * a number of constraints which are not enforced by the HTML5 parsing
- * process.  But the result will be free of gross errors: misnested or
- * unclosed tags, for example, and will be unchanged by spec-complient
- * parsing followed by serialization.
- *
- * The tree building stage is structured as a state machine.
- * When comparing the implementation to
- * https://www.w3.org/TR/html5/syntax.html#tree-construction
- * note that each state is implemented as a function with a
- * name ending in `Mode` (because the HTML spec refers to them
- * as insertion modes).  The current insertion mode is held by
- * the $parseMode property.
- *
- * The following simplifications have been made:
- * - We handle body content only (ie, we start `in body`.)
- * - The document is never in "quirks mode".
- * - All occurrences of < and > have been entity escaped, so we
- *   can parse tags by simply splitting on those two characters.
- *   (This also simplifies the handling of < inside <textarea>.)
- *   The character < must not appear inside comments.
- *   Similarly, all attributes have been "cleaned" and are double-quoted
- *   and escaped.
- * - All null characters are assumed to have been removed.
- * - The following elements are disallowed: <html>, <head>, <body>, <frameset>,
- *   <frame>, <plaintext>, <xmp>, <iframe>,
- *   <noembed>, <noscript>, <script>, <title>.  As a result,
- *   further simplifications can be made:
- *   - `frameset-ok` is not tracked.
- *   - `head element pointer` is not tracked (but presumed non-null)
- *   - Tokenizer has only a single mode. (<textarea> wants RCDATA and
- *     <style>/<noframes> want RAWTEXT modes which we only loosely emulate.)
- *
- *   We generally mark places where we omit cases from the spec due to
- *   disallowed elements with a comment: `// OMITTED: <element-name>`.
- *
- *   The HTML spec keeps a flag during the parsing process to track
- *   whether or not a "parse error" has been encountered.  We don't
- *   bother to track that flag, we just implement the error-handling
- *   process as specified.
- *
- * @ingroup Parser
- * @since 1.27
- * @see https://html.spec.whatwg.org/multipage/syntax.html#tree-construction
- */
-class Balancer {
-       private $parseMode;
-       /** @var \Iterator */
-       private $bitsIterator;
-       private $allowedHtmlElements;
-       /** @var BalanceActiveFormattingElements */
-       private $afe;
-       /** @var BalanceStack */
-       private $stack;
-       private $strict;
-       private $allowComments;
-       private $config;
-
-       private $textIntegrationMode;
-       private $pendingTableText;
-       private $originalInsertionMode;
-       private $fragmentContext;
-       private $formElementPointer;
-       private $ignoreLinefeed;
-       private $inRCDATA;
-       private $inRAWTEXT;
-
-       /** @var callable|null */
-       private $processingCallback;
-       /** @var array */
-       private $processingArgs;
-
-       /**
-        * Valid HTML5 comments.
-        * Regex borrowed from Tim Starling's "remex-html" project.
-        */
-       const VALID_COMMENT_REGEX = "~ !--
-               (                           # 1. Comment match detector
-                       > | -> | # Invalid short close
-                       (                         # 2. Comment contents
-                               (?:
-                                       (?! --> )
-                                       (?! --!> )
-                                       (?! --! \z )
-                                       (?! -- \z )
-                                       (?! - \z )
-                                       .
-                               )*+
-                       )
-                       (                         # 3. Comment close
-                               --> |   # Normal close
-                               --!> |  # Comment end bang
-                               (                       # 4. Indicate matches requiring EOF
-                                       --! |                   # EOF in comment end bang state
-                                       -- |                    # EOF in comment end state
-                                       -  |                    # EOF in comment end dash state
-                                       (?#nothing)             # EOF in comment state
-                               )
-                       )
-               )
-               ([^<]*) \z                  # 5. Non-tag text after the comment
-               ~xs";
-
-       /**
-        * Create a new Balancer.
-        * @param array $config Balancer configuration.  Includes:
-        *     'strict' : boolean, defaults to false.
-        *         When true, enforces syntactic constraints on input:
-        *         all non-tag '<' must be escaped, all attributes must be
-        *         separated by a single space and double-quoted.  This is
-        *         consistent with the output of the Sanitizer.
-        *     'allowedHtmlElements' : array, defaults to null.
-        *         When present, the keys of this associative array give
-        *         the acceptable HTML tag names.  When not present, no
-        *         tag sanitization is done.
-        *     'tidyCompat' : boolean, defaults to false.
-        *         When true, the serialization algorithm is tweaked to
-        *         provide historical compatibility with the old "tidy"
-        *         program: <p>-wrapping is done to the children of
-        *         <body> and <blockquote> elements, and empty elements
-        *         are removed.  The <pre>/<listing>/<textarea> serialization
-        *         is also tweaked to allow lossless round trips.
-        *         (See: https://github.com/whatwg/html/issues/944)
-        *     'allowComments': boolean, defaults to true.
-        *         When true, allows HTML comments in the input.
-        *         The Sanitizer generally strips all comments, so if you
-        *         are running on sanitized output you can set this to
-        *         false to get a bit more performance.
-        */
-       public function __construct( array $config = [] ) {
-               $this->config = $config = $config + [
-                       'strict' => false,
-                       'allowedHtmlElements' => null,
-                       'tidyCompat' => false,
-                       'allowComments' => true,
-               ];
-               $this->allowedHtmlElements = $config['allowedHtmlElements'];
-               $this->strict = $config['strict'];
-               $this->allowComments = $config['allowComments'];
-               if ( $this->allowedHtmlElements !== null ) {
-                       // Sanity check!
-                       $bad = array_uintersect_assoc(
-                               $this->allowedHtmlElements,
-                               BalanceSets::$unsupportedSet[BalanceSets::HTML_NAMESPACE],
-                               function ( $a, $b ) {
-                                       // Ignore the values (just intersect the keys) by saying
-                                       // all values are equal to each other.
-                                       return 0;
-                               }
-                       );
-                       if ( count( $bad ) > 0 ) {
-                               $badstr = implode( ',', array_keys( $bad ) );
-                               throw new ParameterAssertionException(
-                                       '$config',
-                                       'Balance attempted with sanitization including ' .
-                                       "unsupported elements: {$badstr}"
-                               );
-                       }
-               }
-       }
-
-       /**
-        * Return a balanced HTML string for the HTML fragment given by $text,
-        * subject to the caveats listed in the class description.  The result
-        * will typically be idempotent -- that is, rebalancing the output
-        * would result in no change.
-        *
-        * @param string $text The markup to be balanced
-        * @param callable $processingCallback Callback to do any variable or
-        *   parameter replacements in HTML attributes values
-        * @param array|bool $processingArgs Arguments for the processing callback
-        * @return string The balanced markup
-        */
-       public function balance( $text, $processingCallback = null, $processingArgs = [] ) {
-               $this->parseMode = 'inBodyMode';
-               $this->bitsIterator = new ExplodeIterator( '<', $text );
-               $this->afe = new BalanceActiveFormattingElements();
-               $this->stack = new BalanceStack( $this->config );
-               $this->processingCallback = $processingCallback;
-               $this->processingArgs = $processingArgs;
-
-               $this->textIntegrationMode =
-                       $this->ignoreLinefeed =
-                       $this->inRCDATA =
-                       $this->inRAWTEXT = false;
-
-               // The stack is constructed with an <html> element already on it.
-               // Set this up as a fragment parsed with <body> as the context.
-               $this->fragmentContext =
-                       new BalanceElement( BalanceSets::HTML_NAMESPACE, 'body', [] );
-               $this->resetInsertionMode();
-               $this->formElementPointer = null;
-               for ( $e = $this->fragmentContext; $e != null; $e = $e->parent ) {
-                       if ( $e->isHtmlNamed( 'form' ) ) {
-                               $this->formElementPointer = $e;
-                               break;
-                       }
-               }
-
-               // First element is text not tag
-               $x = $this->bitsIterator->current();
-               $this->bitsIterator->next();
-               $this->insertToken( 'text', str_replace( '>', '&gt;', $x ) );
-               // Now process each tag.
-               while ( $this->bitsIterator->valid() ) {
-                       $this->advance();
-               }
-               $this->insertToken( 'eof', null );
-               $result = $this->stack->getOutput();
-               // Free memory before returning.
-               $this->bitsIterator = null;
-               $this->afe = null;
-               $this->stack = null;
-               $this->fragmentContext = null;
-               $this->formElementPointer = null;
-               return $result;
-       }
-
-       /**
-        * Pass a token to the tree builder.  The $token will be one of the
-        * strings "tag", "endtag", or "text".
-        */
-       private function insertToken( $token, $value, $attribs = null, $selfClose = false ) {
-               // validate tags against $unsupportedSet
-               if ( $token === 'tag' || $token === 'endtag' ) {
-                       if ( isset( BalanceSets::$unsupportedSet[BalanceSets::HTML_NAMESPACE][$value] ) ) {
-                               // As described in "simplifications" above, these tags are
-                               // not supported in the balancer.
-                               Assert::invariant(
-                                       !$this->strict,
-                                       "Unsupported $token <$value> found."
-                               );
-                               return false;
-                       }
-               } elseif ( $token === 'text' && $value === '' ) {
-                       // Don't actually inject the empty string as a text token.
-                       return true;
-               }
-               // Support pre/listing/textarea by suppressing initial linefeed
-               if ( $this->ignoreLinefeed ) {
-                       $this->ignoreLinefeed = false;
-                       if ( $token === 'text' ) {
-                               if ( $value[0] === "\n" ) {
-                                       if ( $value === "\n" ) {
-                                               // Nothing would be left, don't inject the empty string.
-                                               return true;
-                                       }
-                                       $value = substr( $value, 1 );
-                               }
-                       }
-               }
-               // Some hoops we have to jump through
-               $adjusted = $this->stack->adjustedCurrentNode( $this->fragmentContext );
-
-               // The spec calls this the "tree construction dispatcher".
-               $isForeign = true;
-               if (
-                       $this->stack->length() === 0 ||
-                       $adjusted->isHtml() ||
-                       $token === 'eof'
-               ) {
-                       $isForeign = false;
-               } elseif ( $adjusted->isMathmlTextIntegrationPoint() ) {
-                       if ( $token === 'text' ) {
-                               $isForeign = false;
-                       } elseif (
-                               $token === 'tag' &&
-                               $value !== 'mglyph' && $value !== 'malignmark'
-                       ) {
-                               $isForeign = false;
-                       }
-               } elseif (
-                       $adjusted->namespaceURI === BalanceSets::MATHML_NAMESPACE &&
-                       $adjusted->localName === 'annotation-xml' &&
-                       $token === 'tag' && $value === 'svg'
-               ) {
-                       $isForeign = false;
-               } elseif (
-                       $adjusted->isHtmlIntegrationPoint() &&
-                       ( $token === 'tag' || $token === 'text' )
-               ) {
-                       $isForeign = false;
-               }
-               if ( $isForeign ) {
-                       return $this->insertForeignToken( $token, $value, $attribs, $selfClose );
-               } else {
-                       $func = $this->parseMode;
-                       return $this->$func( $token, $value, $attribs, $selfClose );
-               }
-       }
-
-       private function insertForeignToken( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'text' ) {
-                       $this->stack->insertText( $value );
-                       return true;
-               } elseif ( $token === 'comment' ) {
-                       $this->stack->insertComment( $value );
-                       return true;
-               } elseif ( $token === 'tag' ) {
-                       switch ( $value ) {
-                               case 'font':
-                                       if ( isset( $attribs['color'] )
-                                               || isset( $attribs['face'] )
-                                               || isset( $attribs['size'] )
-                                       ) {
-                                               break;
-                                       }
-                                       // otherwise, fall through
-                               case 'b':
-                               case 'big':
-                               case 'blockquote':
-                               case 'body':
-                               case 'br':
-                               case 'center':
-                               case 'code':
-                               case 'dd':
-                               case 'div':
-                               case 'dl':
-                               case 'dt':
-                               case 'em':
-                               case 'embed':
-                               case 'h1':
-                               case 'h2':
-                               case 'h3':
-                               case 'h4':
-                               case 'h5':
-                               case 'h6':
-                               case 'head':
-                               case 'hr':
-                               case 'i':
-                               case 'img':
-                               case 'li':
-                               case 'listing':
-                               case 'menu':
-                               case 'meta':
-                               case 'nobr':
-                               case 'ol':
-                               case 'p':
-                               case 'pre':
-                               case 'ruby':
-                               case 's':
-                               case 'small':
-                               case 'span':
-                               case 'strong':
-                               case 'strike':
-                               case 'sub':
-                               case 'sup':
-                               case 'table':
-                               case 'tt':
-                               case 'u':
-                               case 'ul':
-                               case 'var':
-                                       if ( $this->fragmentContext ) {
-                                               break;
-                                       }
-                                       while ( true ) {
-                                               $this->stack->pop();
-                                               $node = $this->stack->currentNode;
-                                               if (
-                                                       $node->isMathmlTextIntegrationPoint() ||
-                                                       $node->isHtmlIntegrationPoint() ||
-                                                       $node->isHtml()
-                                               ) {
-                                                       break;
-                                               }
-                                       }
-                                       return $this->insertToken( $token, $value, $attribs, $selfClose );
-                       }
-                       // "Any other start tag"
-                       $adjusted = ( $this->fragmentContext && $this->stack->length() === 1 ) ?
-                               $this->fragmentContext : $this->stack->currentNode;
-                       $this->stack->insertForeignElement(
-                               $adjusted->namespaceURI, $value, $attribs
-                       );
-                       if ( $selfClose ) {
-                               $this->stack->pop();
-                       }
-                       return true;
-               } elseif ( $token === 'endtag' ) {
-                       $first = true;
-                       foreach ( $this->stack as $i => $node ) {
-                               if ( $node->isHtml() && !$first ) {
-                                       // process the end tag as HTML
-                                       $func = $this->parseMode;
-                                       return $this->$func( $token, $value, $attribs, $selfClose );
-                               } elseif ( $i === 0 ) {
-                                       return true;
-                               } elseif ( $node->localName === $value ) {
-                                       $this->stack->popTag( $node );
-                                       return true;
-                               }
-                               $first = false;
-                       }
-               }
-       }
-
-       /**
-        * Grab the next "token" from $bitsIterator.  This is either a open/close
-        * tag or text or a comment, depending on whether the Sanitizer approves.
-        */
-       private function advance() {
-               $x = $this->bitsIterator->current();
-               $this->bitsIterator->next();
-               $regs = [];
-               // Handle comments.  These won't be generated by mediawiki (they
-               // are stripped in the Sanitizer) but may be generated by extensions.
-               if (
-                       $this->allowComments &&
-                       !( $this->inRCDATA || $this->inRAWTEXT ) &&
-                       preg_match( self::VALID_COMMENT_REGEX, $x, $regs, PREG_OFFSET_CAPTURE ) &&
-                       // verify EOF condition where necessary
-                       ( $regs[4][1] < 0 || !$this->bitsIterator->valid() )
-               ) {
-                       $contents = $regs[2][0];
-                       $rest = $regs[5][0];
-                       $this->insertToken( 'comment', $contents );
-                       $this->insertToken( 'text', str_replace( '>', '&gt;', $rest ) );
-                       return;
-               }
-               // $slash: Does the current element start with a '/'?
-               // $t: Current element name
-               // $attribStr: String between element name and >
-               // $brace: Ending '>' or '/>'
-               // $rest: Everything until the next element from the $bitsIterator
-               if ( preg_match( Sanitizer::ELEMENT_BITS_REGEX, $x, $regs ) ) {
-                       list( /* $qbar */, $slash, $t, $attribStr, $brace, $rest ) = $regs;
-                       $t = strtolower( $t );
-                       if ( $this->strict ) {
-                               // Verify that attributes are all properly double-quoted
-                               Assert::invariant(
-                                       preg_match(
-                                               '/^( [:_A-Z0-9][-.:_A-Z0-9]*="[^"]*")*[ ]*$/i', $attribStr
-                                       ),
-                                       "Bad attribute string found"
-                               );
-                       }
-               } else {
-                       Assert::invariant(
-                               !$this->strict, "< found which does not start a valid tag"
-                       );
-                       $slash = $t = $attribStr = $brace = $rest = null;
-               }
-               $goodTag = $t;
-               if ( $this->inRCDATA ) {
-                       if ( $slash && $t === $this->inRCDATA ) {
-                               $this->inRCDATA = false;
-                       } else {
-                               // No tags allowed; this emulates the "rcdata" tokenizer mode.
-                               $goodTag = false;
-                       }
-               }
-               if ( $this->inRAWTEXT ) {
-                       if ( $slash && $t === $this->inRAWTEXT ) {
-                               $this->inRAWTEXT = false;
-                       } else {
-                               // No tags allowed, no entity-escaping done.
-                               $goodTag = false;
-                       }
-               }
-               $sanitize = $this->allowedHtmlElements !== null;
-               if ( $sanitize ) {
-                       $goodTag = $t && isset( $this->allowedHtmlElements[$t] );
-               }
-               if ( $goodTag ) {
-                       if ( is_callable( $this->processingCallback ) ) {
-                               call_user_func_array( $this->processingCallback, [ &$attribStr, $this->processingArgs ] );
-                       }
-                       if ( $sanitize ) {
-                               $goodTag = Sanitizer::validateTag( $attribStr, $t );
-                       }
-               }
-               if ( $goodTag ) {
-                       if ( $sanitize ) {
-                               $attribs = Sanitizer::decodeTagAttributes( $attribStr );
-                               $attribs = Sanitizer::validateTagAttributes( $attribs, $t );
-                       } else {
-                               $attribs = Sanitizer::decodeTagAttributes( $attribStr );
-                       }
-                       $goodTag = $this->insertToken(
-                               $slash ? 'endtag' : 'tag', $t, $attribs, $brace === '/>'
-                       );
-               }
-               if ( $goodTag ) {
-                       $rest = str_replace( '>', '&gt;', $rest );
-                       $this->insertToken( 'text', str_replace( '>', '&gt;', $rest ) );
-               } elseif ( $this->inRAWTEXT ) {
-                       $this->insertToken( 'text', "<$x" );
-               } else {
-                       // bad tag; serialize entire thing as text.
-                       $this->insertToken( 'text', '&lt;' . str_replace( '>', '&gt;', $x ) );
-               }
-       }
-
-       private function switchMode( $mode ) {
-               Assert::parameter(
-                       substr( $mode, -4 ) === 'Mode', '$mode', 'should end in Mode'
-               );
-               $oldMode = $this->parseMode;
-               $this->parseMode = $mode;
-               return $oldMode;
-       }
-
-       private function switchModeAndReprocess( $mode, $token, $value, $attribs, $selfClose ) {
-               $this->switchMode( $mode );
-               return $this->insertToken( $token, $value, $attribs, $selfClose );
-       }
-
-       private function resetInsertionMode() {
-               $last = false;
-               foreach ( $this->stack as $i => $node ) {
-                       if ( $i === 0 ) {
-                               $last = true;
-                               if ( $this->fragmentContext ) {
-                                       $node = $this->fragmentContext;
-                               }
-                       }
-                       if ( $node->isHtml() ) {
-                               switch ( $node->localName ) {
-                                       case 'select':
-                                               $stackLength = $this->stack->length();
-                                               for ( $j = $i + 1; $j < $stackLength - 1; $j++ ) {
-                                                       $ancestor = $this->stack->node( $stackLength - $j - 1 );
-                                                       if ( $ancestor->isHtmlNamed( 'template' ) ) {
-                                                               break;
-                                                       }
-                                                       if ( $ancestor->isHtmlNamed( 'table' ) ) {
-                                                               $this->switchMode( 'inSelectInTableMode' );
-                                                               return;
-                                                       }
-                                               }
-                                               $this->switchMode( 'inSelectMode' );
-                                               return;
-                                       case 'tr':
-                                               $this->switchMode( 'inRowMode' );
-                                               return;
-                                       case 'tbody':
-                                       case 'tfoot':
-                                       case 'thead':
-                                               $this->switchMode( 'inTableBodyMode' );
-                                               return;
-                                       case 'caption':
-                                               $this->switchMode( 'inCaptionMode' );
-                                               return;
-                                       case 'colgroup':
-                                               $this->switchMode( 'inColumnGroupMode' );
-                                               return;
-                                       case 'table':
-                                               $this->switchMode( 'inTableMode' );
-                                               return;
-                                       case 'template':
-                                               $this->switchMode(
-                                                       array_slice( $this->templateInsertionModes, -1 )[0]
-                                               );
-                                               return;
-                                       case 'body':
-                                               $this->switchMode( 'inBodyMode' );
-                                               return;
-                                       // OMITTED: <frameset>
-                                       // OMITTED: <html>
-                                       // OMITTED: <head>
-                                       default:
-                                               if ( !$last ) {
-                                                       // OMITTED: <head>
-                                                       if ( $node->isA( BalanceSets::$tableCellSet ) ) {
-                                                               $this->switchMode( 'inCellMode' );
-                                                               return;
-                                                       }
-                                               }
-                               }
-                       }
-                       if ( $last ) {
-                               $this->switchMode( 'inBodyMode' );
-                               return;
-                       }
-               }
-       }
-
-       private function stopParsing() {
-               // Most of the spec methods are inapplicable, other than step 2:
-               // "pop all the nodes off the stack of open elements".
-               // We're going to keep the top-most <html> element on the stack, though.
-
-               // Clear the AFE list first, otherwise the element objects will stay live
-               // during serialization, potentially using O(N^2) memory. Note that
-               // popping the stack will never result in reconstructing the active
-               // formatting elements.
-               $this->afe = null;
-               $this->stack->popTo( 1 );
-       }
-
-       private function parseRawText( $value, $attribs = null ) {
-               $this->stack->insertHTMLElement( $value, $attribs );
-               $this->inRAWTEXT = $value;
-               $this->originalInsertionMode = $this->switchMode( 'inTextMode' );
-               return true;
-       }
-
-       private function inTextMode( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'text' ) {
-                       $this->stack->insertText( $value );
-                       return true;
-               } elseif ( $token === 'eof' ) {
-                       $this->stack->pop();
-                       return $this->switchModeAndReprocess(
-                               $this->originalInsertionMode, $token, $value, $attribs, $selfClose
-                       );
-               } elseif ( $token === 'endtag' ) {
-                       $this->stack->pop();
-                       $this->switchMode( $this->originalInsertionMode );
-                       return true;
-               }
-               return true;
-       }
-
-       private function inHeadMode( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'text' ) {
-                       if ( preg_match( '/^[\x09\x0A\x0C\x0D\x20]+/', $value, $matches ) ) {
-                               $this->stack->insertText( $matches[0] );
-                               $value = substr( $value, strlen( $matches[0] ) );
-                       }
-                       if ( strlen( $value ) === 0 ) {
-                               return true; // All text handled.
-                       }
-                       // Fall through to handle non-whitespace below.
-               } elseif ( $token === 'tag' ) {
-                       switch ( $value ) {
-                               case 'meta':
-                                       // OMITTED: in a full HTML parser, this might change the encoding.
-                                       // falls through
-                               // OMITTED: <html>
-                               case 'base':
-                               case 'basefont':
-                               case 'bgsound':
-                               case 'link':
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->stack->pop();
-                                       return true;
-                               // OMITTED: <title>
-                               // OMITTED: <noscript>
-                               case 'noframes':
-                               case 'style':
-                                       return $this->parseRawText( $value, $attribs );
-                               // OMITTED: <script>
-                               case 'template':
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->afe->insertMarker();
-                                       // OMITTED: frameset_ok
-                                       $this->switchMode( 'inTemplateMode' );
-                                       $this->templateInsertionModes[] = $this->parseMode;
-                                       return true;
-                               // OMITTED: <head>
-                       }
-               } elseif ( $token === 'endtag' ) {
-                       switch ( $value ) {
-                               // OMITTED: <head>
-                               // OMITTED: <body>
-                               // OMITTED: <html>
-                               case 'br':
-                                       break; // handle at the bottom of the function
-                               case 'template':
-                                       if ( $this->stack->indexOf( $value ) < 0 ) {
-                                               return true; // Ignore the token.
-                                       }
-                                       $this->stack->generateImpliedEndTags( null, true /* thorough */ );
-                                       $this->stack->popTag( $value );
-                                       $this->afe->clearToMarker();
-                                       array_pop( $this->templateInsertionModes );
-                                       $this->resetInsertionMode();
-                                       return true;
-                               default:
-                                       // ignore any other end tag
-                                       return true;
-                       }
-               } elseif ( $token === 'comment' ) {
-                       $this->stack->insertComment( $value );
-                       return true;
-               }
-
-               // If not handled above
-               $this->inHeadMode( 'endtag', 'head' ); // synthetic </head>
-               // Then redo this one
-               return $this->insertToken( $token, $value, $attribs, $selfClose );
-       }
-
-       private function inBodyMode( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'text' ) {
-                       $this->afe->reconstruct( $this->stack );
-                       $this->stack->insertText( $value );
-                       return true;
-               } elseif ( $token === 'eof' ) {
-                       if ( !empty( $this->templateInsertionModes ) ) {
-                               return $this->inTemplateMode( $token, $value, $attribs, $selfClose );
-                       }
-                       $this->stopParsing();
-                       return true;
-               } elseif ( $token === 'tag' ) {
-                       switch ( $value ) {
-                               // OMITTED: <html>
-                               case 'base':
-                               case 'basefont':
-                               case 'bgsound':
-                               case 'link':
-                               case 'meta':
-                               case 'noframes':
-                               // OMITTED: <script>
-                               case 'style':
-                               case 'template':
-                               // OMITTED: <title>
-                                       return $this->inHeadMode( $token, $value, $attribs, $selfClose );
-                               // OMITTED: <body>
-                               // OMITTED: <frameset>
-
-                               case 'address':
-                               case 'article':
-                               case 'aside':
-                               case 'blockquote':
-                               case 'center':
-                               case 'details':
-                               case 'dialog':
-                               case 'dir':
-                               case 'div':
-                               case 'dl':
-                               case 'fieldset':
-                               case 'figcaption':
-                               case 'figure':
-                               case 'footer':
-                               case 'header':
-                               case 'hgroup':
-                               case 'main':
-                               case 'nav':
-                               case 'ol':
-                               case 'p':
-                               case 'section':
-                               case 'summary':
-                               case 'ul':
-                                       if ( $this->stack->inButtonScope( 'p' ) ) {
-                                               $this->inBodyMode( 'endtag', 'p' );
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       return true;
-
-                               case 'menu':
-                                       if ( $this->stack->inButtonScope( "p" ) ) {
-                                               $this->inBodyMode( 'endtag', 'p' );
-                                       }
-                                       if ( $this->stack->currentNode->isHtmlNamed( 'menuitem' ) ) {
-                                               $this->stack->pop();
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       return true;
-
-                               case 'h1':
-                               case 'h2':
-                               case 'h3':
-                               case 'h4':
-                               case 'h5':
-                               case 'h6':
-                                       if ( $this->stack->inButtonScope( 'p' ) ) {
-                                               $this->inBodyMode( 'endtag', 'p' );
-                                       }
-                                       if ( $this->stack->currentNode->isA( BalanceSets::$headingSet ) ) {
-                                               $this->stack->pop();
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       return true;
-
-                               case 'pre':
-                               case 'listing':
-                                       if ( $this->stack->inButtonScope( 'p' ) ) {
-                                               $this->inBodyMode( 'endtag', 'p' );
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->ignoreLinefeed = true;
-                                       // OMITTED: frameset_ok
-                                       return true;
-
-                               case 'form':
-                                       if (
-                                               $this->formElementPointer &&
-                                               $this->stack->indexOf( 'template' ) < 0
-                                       ) {
-                                               return true; // in a form, not in a template.
-                                       }
-                                       if ( $this->stack->inButtonScope( "p" ) ) {
-                                               $this->inBodyMode( 'endtag', 'p' );
-                                       }
-                                       $elt = $this->stack->insertHTMLElement( $value, $attribs );
-                                       if ( $this->stack->indexOf( 'template' ) < 0 ) {
-                                               $this->formElementPointer = $elt;
-                                       }
-                                       return true;
-
-                               case 'li':
-                                       // OMITTED: frameset_ok
-                                       foreach ( $this->stack as $node ) {
-                                               if ( $node->isHtmlNamed( 'li' ) ) {
-                                                       $this->inBodyMode( 'endtag', 'li' );
-                                                       break;
-                                               }
-                                               if (
-                                                       $node->isA( BalanceSets::$specialSet ) &&
-                                                       !$node->isA( BalanceSets::$addressDivPSet )
-                                               ) {
-                                                       break;
-                                               }
-                                       }
-                                       if ( $this->stack->inButtonScope( 'p' ) ) {
-                                               $this->inBodyMode( 'endtag', 'p' );
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       return true;
-
-                               case 'dd':
-                               case 'dt':
-                                       // OMITTED: frameset_ok
-                                       foreach ( $this->stack as $node ) {
-                                               if ( $node->isHtmlNamed( 'dd' ) ) {
-                                                       $this->inBodyMode( 'endtag', 'dd' );
-                                                       break;
-                                               }
-                                               if ( $node->isHtmlNamed( 'dt' ) ) {
-                                                       $this->inBodyMode( 'endtag', 'dt' );
-                                                       break;
-                                               }
-                                               if (
-                                                       $node->isA( BalanceSets::$specialSet ) &&
-                                                       !$node->isA( BalanceSets::$addressDivPSet )
-                                               ) {
-                                                       break;
-                                               }
-                                       }
-                                       if ( $this->stack->inButtonScope( 'p' ) ) {
-                                               $this->inBodyMode( 'endtag', 'p' );
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       return true;
-
-                               // OMITTED: <plaintext>
-
-                               case 'button':
-                                       if ( $this->stack->inScope( 'button' ) ) {
-                                               $this->inBodyMode( 'endtag', 'button' );
-                                               return $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       }
-                                       $this->afe->reconstruct( $this->stack );
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       return true;
-
-                               case 'a':
-                                       $activeElement = $this->afe->findElementByTag( 'a' );
-                                       if ( $activeElement ) {
-                                               $this->inBodyMode( 'endtag', 'a' );
-                                               if ( $this->afe->isInList( $activeElement ) ) {
-                                                       $this->afe->remove( $activeElement );
-                                                       // Don't flatten here, since when we fall
-                                                       // through below we might foster parent
-                                                       // the new <a> tag inside this one.
-                                                       $this->stack->removeElement( $activeElement, false );
-                                               }
-                                       }
-                                       // Falls through
-                               case 'b':
-                               case 'big':
-                               case 'code':
-                               case 'em':
-                               case 'font':
-                               case 'i':
-                               case 's':
-                               case 'small':
-                               case 'strike':
-                               case 'strong':
-                               case 'tt':
-                               case 'u':
-                                       $this->afe->reconstruct( $this->stack );
-                                       $this->afe->push( $this->stack->insertHTMLElement( $value, $attribs ) );
-                                       return true;
-
-                               case 'nobr':
-                                       $this->afe->reconstruct( $this->stack );
-                                       if ( $this->stack->inScope( 'nobr' ) ) {
-                                               $this->inBodyMode( 'endtag', 'nobr' );
-                                               $this->afe->reconstruct( $this->stack );
-                                       }
-                                       $this->afe->push( $this->stack->insertHTMLElement( $value, $attribs ) );
-                                       return true;
-
-                               case 'applet':
-                               case 'marquee':
-                               case 'object':
-                                       $this->afe->reconstruct( $this->stack );
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->afe->insertMarker();
-                                       // OMITTED: frameset_ok
-                                       return true;
-
-                               case 'table':
-                                       // The document is never in "quirks mode"; see simplifications
-                                       // above.
-                                       if ( $this->stack->inButtonScope( 'p' ) ) {
-                                               $this->inBodyMode( 'endtag', 'p' );
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       // OMITTED: frameset_ok
-                                       $this->switchMode( 'inTableMode' );
-                                       return true;
-
-                               case 'area':
-                               case 'br':
-                               case 'embed':
-                               case 'img':
-                               case 'keygen':
-                               case 'wbr':
-                                       $this->afe->reconstruct( $this->stack );
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->stack->pop();
-                                       // OMITTED: frameset_ok
-                                       return true;
-
-                               case 'input':
-                                       $this->afe->reconstruct( $this->stack );
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->stack->pop();
-                                       // OMITTED: frameset_ok
-                                       // (hence we don't need to examine the tag's "type" attribute)
-                                       return true;
-
-                               case 'param':
-                               case 'source':
-                               case 'track':
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->stack->pop();
-                                       return true;
-
-                               case 'hr':
-                                       if ( $this->stack->inButtonScope( 'p' ) ) {
-                                               $this->inBodyMode( 'endtag', 'p' );
-                                       }
-                                       if ( $this->stack->currentNode->isHtmlNamed( 'menuitem' ) ) {
-                                               $this->stack->pop();
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->stack->pop();
-                                       return true;
-
-                               case 'image':
-                                       // warts!
-                                       return $this->inBodyMode( $token, 'img', $attribs, $selfClose );
-
-                               case 'textarea':
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->ignoreLinefeed = true;
-                                       $this->inRCDATA = $value; // emulate rcdata tokenizer mode
-                                       // OMITTED: frameset_ok
-                                       return true;
-
-                               // OMITTED: <xmp>
-                               // OMITTED: <iframe>
-                               // OMITTED: <noembed>
-                               // OMITTED: <noscript>
-
-                               case 'select':
-                                       $this->afe->reconstruct( $this->stack );
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       switch ( $this->parseMode ) {
-                                               case 'inTableMode':
-                                               case 'inCaptionMode':
-                                               case 'inTableBodyMode':
-                                               case 'inRowMode':
-                                               case 'inCellMode':
-                                                       $this->switchMode( 'inSelectInTableMode' );
-                                                       return true;
-                                               default:
-                                                       $this->switchMode( 'inSelectMode' );
-                                                       return true;
-                                       }
-
-                               case 'optgroup':
-                               case 'option':
-                                       if ( $this->stack->currentNode->isHtmlNamed( 'option' ) ) {
-                                               $this->inBodyMode( 'endtag', 'option' );
-                                       }
-                                       $this->afe->reconstruct( $this->stack );
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       return true;
-
-                               case 'menuitem':
-                                       if ( $this->stack->currentNode->isHtmlNamed( 'menuitem' ) ) {
-                                               $this->stack->pop();
-                                       }
-                                       $this->afe->reconstruct( $this->stack );
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       return true;
-
-                               case 'rb':
-                               case 'rtc':
-                                       if ( $this->stack->inScope( 'ruby' ) ) {
-                                               $this->stack->generateImpliedEndTags();
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       return true;
-
-                               case 'rp':
-                               case 'rt':
-                                       if ( $this->stack->inScope( 'ruby' ) ) {
-                                               $this->stack->generateImpliedEndTags( 'rtc' );
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       return true;
-
-                               case 'math':
-                                       $this->afe->reconstruct( $this->stack );
-                                       // We skip the spec's "adjust MathML attributes" and
-                                       // "adjust foreign attributes" steps, since the browser will
-                                       // do this later when it parses the output and it doesn't affect
-                                       // balancing.
-                                       $this->stack->insertForeignElement(
-                                               BalanceSets::MATHML_NAMESPACE, $value, $attribs
-                                       );
-                                       if ( $selfClose ) {
-                                               // emit explicit </math> tag.
-                                               $this->stack->pop();
-                                       }
-                                       return true;
-
-                               case 'svg':
-                                       $this->afe->reconstruct( $this->stack );
-                                       // We skip the spec's "adjust SVG attributes" and
-                                       // "adjust foreign attributes" steps, since the browser will
-                                       // do this later when it parses the output and it doesn't affect
-                                       // balancing.
-                                       $this->stack->insertForeignElement(
-                                               BalanceSets::SVG_NAMESPACE, $value, $attribs
-                                       );
-                                       if ( $selfClose ) {
-                                               // emit explicit </svg> tag.
-                                               $this->stack->pop();
-                                       }
-                                       return true;
-
-                               case 'caption':
-                               case 'col':
-                               case 'colgroup':
-                               // OMITTED: <frame>
-                               case 'head':
-                               case 'tbody':
-                               case 'td':
-                               case 'tfoot':
-                               case 'th':
-                               case 'thead':
-                               case 'tr':
-                                       // Ignore table tags if we're not inTableMode
-                                       return true;
-                       }
-
-                       // Handle any other start tag here
-                       $this->afe->reconstruct( $this->stack );
-                       $this->stack->insertHTMLElement( $value, $attribs );
-                       return true;
-               } elseif ( $token === 'endtag' ) {
-                       switch ( $value ) {
-                               // </body>,</html> are unsupported.
-
-                               case 'template':
-                                       return $this->inHeadMode( $token, $value, $attribs, $selfClose );
-
-                               case 'address':
-                               case 'article':
-                               case 'aside':
-                               case 'blockquote':
-                               case 'button':
-                               case 'center':
-                               case 'details':
-                               case 'dialog':
-                               case 'dir':
-                               case 'div':
-                               case 'dl':
-                               case 'fieldset':
-                               case 'figcaption':
-                               case 'figure':
-                               case 'footer':
-                               case 'header':
-                               case 'hgroup':
-                               case 'listing':
-                               case 'main':
-                               case 'menu':
-                               case 'nav':
-                               case 'ol':
-                               case 'pre':
-                               case 'section':
-                               case 'summary':
-                               case 'ul':
-                                       // Ignore if there is not a matching open tag
-                                       if ( !$this->stack->inScope( $value ) ) {
-                                               return true;
-                                       }
-                                       $this->stack->generateImpliedEndTags();
-                                       $this->stack->popTag( $value );
-                                       return true;
-
-                               case 'form':
-                                       if ( $this->stack->indexOf( 'template' ) < 0 ) {
-                                               $openform = $this->formElementPointer;
-                                               $this->formElementPointer = null;
-                                               if ( !$openform || !$this->stack->inScope( $openform ) ) {
-                                                       return true;
-                                               }
-                                               $this->stack->generateImpliedEndTags();
-                                               // Don't flatten yet if we're removing a <form> element
-                                               // out-of-order. (eg. `<form><div></form>`)
-                                               $flatten = ( $this->stack->currentNode === $openform );
-                                               $this->stack->removeElement( $openform, $flatten );
-                                       } else {
-                                               if ( !$this->stack->inScope( 'form' ) ) {
-                                                       return true;
-                                               }
-                                               $this->stack->generateImpliedEndTags();
-                                               $this->stack->popTag( 'form' );
-                                       }
-                                       return true;
-
-                               case 'p':
-                                       if ( !$this->stack->inButtonScope( 'p' ) ) {
-                                               $this->inBodyMode( 'tag', 'p', [] );
-                                               return $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       }
-                                       $this->stack->generateImpliedEndTags( $value );
-                                       $this->stack->popTag( $value );
-                                       return true;
-
-                               case 'li':
-                                       if ( !$this->stack->inListItemScope( $value ) ) {
-                                               return true; // ignore
-                                       }
-                                       $this->stack->generateImpliedEndTags( $value );
-                                       $this->stack->popTag( $value );
-                                       return true;
-
-                               case 'dd':
-                               case 'dt':
-                                       if ( !$this->stack->inScope( $value ) ) {
-                                               return true; // ignore
-                                       }
-                                       $this->stack->generateImpliedEndTags( $value );
-                                       $this->stack->popTag( $value );
-                                       return true;
-
-                               case 'h1':
-                               case 'h2':
-                               case 'h3':
-                               case 'h4':
-                               case 'h5':
-                               case 'h6':
-                                       if ( !$this->stack->inScope( BalanceSets::$headingSet ) ) {
-                                               return true; // ignore
-                                       }
-                                       $this->stack->generateImpliedEndTags();
-                                       $this->stack->popTag( BalanceSets::$headingSet );
-                                       return true;
-
-                               case 'sarcasm':
-                                       // Take a deep breath, then:
-                                       break;
-
-                               case 'a':
-                               case 'b':
-                               case 'big':
-                               case 'code':
-                               case 'em':
-                               case 'font':
-                               case 'i':
-                               case 'nobr':
-                               case 's':
-                               case 'small':
-                               case 'strike':
-                               case 'strong':
-                               case 'tt':
-                               case 'u':
-                                       if ( $this->stack->adoptionAgency( $value, $this->afe ) ) {
-                                               return true; // If we did something, we're done.
-                                       }
-                                       break; // Go to the "any other end tag" case.
-
-                               case 'applet':
-                               case 'marquee':
-                               case 'object':
-                                       if ( !$this->stack->inScope( $value ) ) {
-                                               return true; // ignore
-                                       }
-                                       $this->stack->generateImpliedEndTags();
-                                       $this->stack->popTag( $value );
-                                       $this->afe->clearToMarker();
-                                       return true;
-
-                               case 'br':
-                                       // Turn </br> into <br>
-                                       return $this->inBodyMode( 'tag', $value, [] );
-                       }
-
-                       // Any other end tag goes here
-                       foreach ( $this->stack as $i => $node ) {
-                               if ( $node->isHtmlNamed( $value ) ) {
-                                       $this->stack->generateImpliedEndTags( $value );
-                                       $this->stack->popTo( $i ); // including $i
-                                       break;
-                               } elseif ( $node->isA( BalanceSets::$specialSet ) ) {
-                                       return true; // ignore this close token.
-                               }
-                       }
-                       return true;
-               } elseif ( $token === 'comment' ) {
-                       $this->stack->insertComment( $value );
-                       return true;
-               } else {
-                       Assert::invariant( false, "Bad token type: $token" );
-               }
-       }
-
-       private function inTableMode( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'text' ) {
-                       if ( $this->textIntegrationMode ) {
-                               return $this->inBodyMode( $token, $value, $attribs, $selfClose );
-                       } elseif ( $this->stack->currentNode->isA( BalanceSets::$tableSectionRowSet ) ) {
-                               $this->pendingTableText = '';
-                               $this->originalInsertionMode = $this->parseMode;
-                               return $this->switchModeAndReprocess( 'inTableTextMode',
-                                       $token, $value, $attribs, $selfClose );
-                       }
-                       // fall through to default case.
-               } elseif ( $token === 'eof' ) {
-                       $this->stopParsing();
-                       return true;
-               } elseif ( $token === 'tag' ) {
-                       switch ( $value ) {
-                               case 'caption':
-                                       $this->afe->insertMarker();
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->switchMode( 'inCaptionMode' );
-                                       return true;
-                               case 'colgroup':
-                                       $this->stack->clearToContext( BalanceSets::$tableContextSet );
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->switchMode( 'inColumnGroupMode' );
-                                       return true;
-                               case 'col':
-                                       $this->inTableMode( 'tag', 'colgroup', [] );
-                                       return $this->insertToken( $token, $value, $attribs, $selfClose );
-                               case 'tbody':
-                               case 'tfoot':
-                               case 'thead':
-                                       $this->stack->clearToContext( BalanceSets::$tableContextSet );
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->switchMode( 'inTableBodyMode' );
-                                       return true;
-                               case 'td':
-                               case 'th':
-                               case 'tr':
-                                       $this->inTableMode( 'tag', 'tbody', [] );
-                                       return $this->insertToken( $token, $value, $attribs, $selfClose );
-                               case 'table':
-                                       if ( !$this->stack->inTableScope( $value ) ) {
-                                               return true; // Ignore this tag.
-                                       }
-                                       $this->inTableMode( 'endtag', $value );
-                                       return $this->insertToken( $token, $value, $attribs, $selfClose );
-
-                               case 'style':
-                               // OMITTED: <script>
-                               case 'template':
-                                       return $this->inHeadMode( $token, $value, $attribs, $selfClose );
-
-                               case 'input':
-                                       if ( !isset( $attribs['type'] ) || strcasecmp( $attribs['type'], 'hidden' ) !== 0 ) {
-                                               break; // Handle this as "everything else"
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->stack->pop();
-                                       return true;
-
-                               case 'form':
-                                       if (
-                                               $this->formElementPointer ||
-                                               $this->stack->indexOf( 'template' ) >= 0
-                                       ) {
-                                               return true; // ignore this token
-                                       }
-                                       $this->formElementPointer =
-                                               $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->stack->popTag( $this->formElementPointer );
-                                       return true;
-                       }
-                       // Fall through for "anything else" clause.
-               } elseif ( $token === 'endtag' ) {
-                       switch ( $value ) {
-                               case 'table':
-                                       if ( !$this->stack->inTableScope( $value ) ) {
-                                               return true; // Ignore.
-                                       }
-                                       $this->stack->popTag( $value );
-                                       $this->resetInsertionMode();
-                                       return true;
-                               // OMITTED: <body>
-                               case 'caption':
-                               case 'col':
-                               case 'colgroup':
-                               // OMITTED: <html>
-                               case 'tbody':
-                               case 'td':
-                               case 'tfoot':
-                               case 'th':
-                               case 'thead':
-                               case 'tr':
-                                       return true; // Ignore the token.
-                               case 'template':
-                                       return $this->inHeadMode( $token, $value, $attribs, $selfClose );
-                       }
-                       // Fall through for "anything else" clause.
-               } elseif ( $token === 'comment' ) {
-                       $this->stack->insertComment( $value );
-                       return true;
-               }
-               // This is the "anything else" case:
-               $this->stack->fosterParentMode = true;
-               $this->inBodyMode( $token, $value, $attribs, $selfClose );
-               $this->stack->fosterParentMode = false;
-               return true;
-       }
-
-       private function inTableTextMode( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'text' ) {
-                       $this->pendingTableText .= $value;
-                       return true;
-               }
-               // Non-text token:
-               $text = $this->pendingTableText;
-               $this->pendingTableText = '';
-               if ( preg_match( '/[^\x09\x0A\x0C\x0D\x20]/', $text ) ) {
-                       // This should match the "anything else" case inTableMode
-                       $this->stack->fosterParentMode = true;
-                       $this->inBodyMode( 'text', $text );
-                       $this->stack->fosterParentMode = false;
-               } else {
-                       // Pending text is just whitespace.
-                       $this->stack->insertText( $text );
-               }
-               return $this->switchModeAndReprocess(
-                       $this->originalInsertionMode, $token, $value, $attribs, $selfClose
-               );
-       }
-
-       // helper for inCaptionMode
-       private function endCaption() {
-               if ( !$this->stack->inTableScope( 'caption' ) ) {
-                       return false;
-               }
-               $this->stack->generateImpliedEndTags();
-               $this->stack->popTag( 'caption' );
-               $this->afe->clearToMarker();
-               $this->switchMode( 'inTableMode' );
-               return true;
-       }
-
-       private function inCaptionMode( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'tag' ) {
-                       switch ( $value ) {
-                               case 'caption':
-                               case 'col':
-                               case 'colgroup':
-                               case 'tbody':
-                               case 'td':
-                               case 'tfoot':
-                               case 'th':
-                               case 'thead':
-                               case 'tr':
-                                       if ( $this->endCaption() ) {
-                                               $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       }
-                                       return true;
-                       }
-                       // Fall through to "anything else" case.
-               } elseif ( $token === 'endtag' ) {
-                       switch ( $value ) {
-                               case 'caption':
-                                       $this->endCaption();
-                                       return true;
-                               case 'table':
-                                       if ( $this->endCaption() ) {
-                                               $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       }
-                                       return true;
-                               case 'body':
-                               case 'col':
-                               case 'colgroup':
-                               // OMITTED: <html>
-                               case 'tbody':
-                               case 'td':
-                               case 'tfoot':
-                               case 'th':
-                               case 'thead':
-                               case 'tr':
-                                       // Ignore the token
-                                       return true;
-                       }
-                       // Fall through to "anything else" case.
-               }
-               // The Anything Else case
-               return $this->inBodyMode( $token, $value, $attribs, $selfClose );
-       }
-
-       private function inColumnGroupMode( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'text' ) {
-                       if ( preg_match( '/^[\x09\x0A\x0C\x0D\x20]+/', $value, $matches ) ) {
-                               $this->stack->insertText( $matches[0] );
-                               $value = substr( $value, strlen( $matches[0] ) );
-                       }
-                       if ( strlen( $value ) === 0 ) {
-                               return true; // All text handled.
-                       }
-                       // Fall through to handle non-whitespace below.
-               } elseif ( $token === 'tag' ) {
-                       switch ( $value ) {
-                               // OMITTED: <html>
-                               case 'col':
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->stack->pop();
-                                       return true;
-                               case 'template':
-                                       return $this->inHeadMode( $token, $value, $attribs, $selfClose );
-                       }
-                       // Fall through for "anything else".
-               } elseif ( $token === 'endtag' ) {
-                       switch ( $value ) {
-                               case 'colgroup':
-                                       if ( !$this->stack->currentNode->isHtmlNamed( 'colgroup' ) ) {
-                                               return true; // Ignore the token.
-                                       }
-                                       $this->stack->pop();
-                                       $this->switchMode( 'inTableMode' );
-                                       return true;
-                               case 'col':
-                                       return true; // Ignore the token.
-                               case 'template':
-                                       return $this->inHeadMode( $token, $value, $attribs, $selfClose );
-                       }
-                       // Fall through for "anything else".
-               } elseif ( $token === 'eof' ) {
-                       return $this->inBodyMode( $token, $value, $attribs, $selfClose );
-               } elseif ( $token === 'comment' ) {
-                       $this->stack->insertComment( $value );
-                       return true;
-               }
-
-               // Anything else
-               if ( !$this->stack->currentNode->isHtmlNamed( 'colgroup' ) ) {
-                       return true; // Ignore the token.
-               }
-               $this->inColumnGroupMode( 'endtag', 'colgroup' );
-               return $this->insertToken( $token, $value, $attribs, $selfClose );
-       }
-
-       // Helper function for inTableBodyMode
-       private function endSection() {
-               if ( !(
-                       $this->stack->inTableScope( 'tbody' ) ||
-                       $this->stack->inTableScope( 'thead' ) ||
-                       $this->stack->inTableScope( 'tfoot' )
-               ) ) {
-                       return false;
-               }
-               $this->stack->clearToContext( BalanceSets::$tableBodyContextSet );
-               $this->stack->pop();
-               $this->switchMode( 'inTableMode' );
-               return true;
-       }
-       private function inTableBodyMode( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'tag' ) {
-                       switch ( $value ) {
-                               case 'tr':
-                                       $this->stack->clearToContext( BalanceSets::$tableBodyContextSet );
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->switchMode( 'inRowMode' );
-                                       return true;
-                               case 'th':
-                               case 'td':
-                                       $this->inTableBodyMode( 'tag', 'tr', [] );
-                                       $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       return true;
-                               case 'caption':
-                               case 'col':
-                               case 'colgroup':
-                               case 'tbody':
-                               case 'tfoot':
-                               case 'thead':
-                                       if ( $this->endSection() ) {
-                                               $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       }
-                                       return true;
-                       }
-               } elseif ( $token === 'endtag' ) {
-                       switch ( $value ) {
-                               case 'table':
-                                       if ( $this->endSection() ) {
-                                               $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       }
-                                       return true;
-                               case 'tbody':
-                               case 'tfoot':
-                               case 'thead':
-                                       if ( $this->stack->inTableScope( $value ) ) {
-                                               $this->endSection();
-                                       }
-                                       return true;
-                               // OMITTED: <body>
-                               case 'caption':
-                               case 'col':
-                               case 'colgroup':
-                               // OMITTED: <html>
-                               case 'td':
-                               case 'th':
-                               case 'tr':
-                                       return true; // Ignore the token.
-                       }
-               }
-               // Anything else:
-               return $this->inTableMode( $token, $value, $attribs, $selfClose );
-       }
-
-       // Helper function for inRowMode
-       private function endRow() {
-               if ( !$this->stack->inTableScope( 'tr' ) ) {
-                       return false;
-               }
-               $this->stack->clearToContext( BalanceSets::$tableRowContextSet );
-               $this->stack->pop();
-               $this->switchMode( 'inTableBodyMode' );
-               return true;
-       }
-       private function inRowMode( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'tag' ) {
-                       switch ( $value ) {
-                               case 'th':
-                               case 'td':
-                                       $this->stack->clearToContext( BalanceSets::$tableRowContextSet );
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       $this->switchMode( 'inCellMode' );
-                                       $this->afe->insertMarker();
-                                       return true;
-                               case 'caption':
-                               case 'col':
-                               case 'colgroup':
-                               case 'tbody':
-                               case 'tfoot':
-                               case 'thead':
-                               case 'tr':
-                                       if ( $this->endRow() ) {
-                                               $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       }
-                                       return true;
-                       }
-               } elseif ( $token === 'endtag' ) {
-                       switch ( $value ) {
-                               case 'tr':
-                                       $this->endRow();
-                                       return true;
-                               case 'table':
-                                       if ( $this->endRow() ) {
-                                               $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       }
-                                       return true;
-                               case 'tbody':
-                               case 'tfoot':
-                               case 'thead':
-                                       if (
-                                               $this->stack->inTableScope( $value ) &&
-                                               $this->endRow()
-                                       ) {
-                                               $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       }
-                                       return true;
-                               // OMITTED: <body>
-                               case 'caption':
-                               case 'col':
-                               case 'colgroup':
-                               // OMITTED: <html>
-                               case 'td':
-                               case 'th':
-                                       return true; // Ignore the token.
-                       }
-               }
-               // Anything else:
-               return $this->inTableMode( $token, $value, $attribs, $selfClose );
-       }
-
-       // Helper for inCellMode
-       private function endCell() {
-               if ( $this->stack->inTableScope( 'td' ) ) {
-                       $this->inCellMode( 'endtag', 'td' );
-                       return true;
-               } elseif ( $this->stack->inTableScope( 'th' ) ) {
-                       $this->inCellMode( 'endtag', 'th' );
-                       return true;
-               } else {
-                       return false;
-               }
-       }
-       private function inCellMode( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'tag' ) {
-                       switch ( $value ) {
-                               case 'caption':
-                               case 'col':
-                               case 'colgroup':
-                               case 'tbody':
-                               case 'td':
-                               case 'tfoot':
-                               case 'th':
-                               case 'thead':
-                               case 'tr':
-                                       if ( $this->endCell() ) {
-                                               $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       }
-                                       return true;
-                       }
-               } elseif ( $token === 'endtag' ) {
-                       switch ( $value ) {
-                               case 'td':
-                               case 'th':
-                                       if ( $this->stack->inTableScope( $value ) ) {
-                                               $this->stack->generateImpliedEndTags();
-                                               $this->stack->popTag( $value );
-                                               $this->afe->clearToMarker();
-                                               $this->switchMode( 'inRowMode' );
-                                       }
-                                       return true;
-                               // OMITTED: <body>
-                               case 'caption':
-                               case 'col':
-                               case 'colgroup':
-                               // OMITTED: <html>
-                                       return true;
-
-                               case 'table':
-                               case 'tbody':
-                               case 'tfoot':
-                               case 'thead':
-                               case 'tr':
-                                       if ( $this->stack->inTableScope( $value ) ) {
-                                               $this->stack->generateImpliedEndTags();
-                                               $this->stack->popTag( BalanceSets::$tableCellSet );
-                                               $this->afe->clearToMarker();
-                                               $this->switchMode( 'inRowMode' );
-                                               $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       }
-                                       return true;
-                       }
-               }
-               // Anything else:
-               return $this->inBodyMode( $token, $value, $attribs, $selfClose );
-       }
-
-       private function inSelectMode( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'text' ) {
-                       $this->stack->insertText( $value );
-                       return true;
-               } elseif ( $token === 'eof' ) {
-                       return $this->inBodyMode( $token, $value, $attribs, $selfClose );
-               } elseif ( $token === 'tag' ) {
-                       switch ( $value ) {
-                               // OMITTED: <html>
-                               case 'option':
-                                       if ( $this->stack->currentNode->isHtmlNamed( 'option' ) ) {
-                                               $this->stack->pop();
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       return true;
-                               case 'optgroup':
-                                       if ( $this->stack->currentNode->isHtmlNamed( 'option' ) ) {
-                                               $this->stack->pop();
-                                       }
-                                       if ( $this->stack->currentNode->isHtmlNamed( 'optgroup' ) ) {
-                                               $this->stack->pop();
-                                       }
-                                       $this->stack->insertHTMLElement( $value, $attribs );
-                                       return true;
-                               case 'select':
-                                       $this->inSelectMode( 'endtag', $value ); // treat it like endtag
-                                       return true;
-                               case 'input':
-                               case 'keygen':
-                               case 'textarea':
-                                       if ( !$this->stack->inSelectScope( 'select' ) ) {
-                                               return true; // ignore token (fragment case)
-                                       }
-                                       $this->inSelectMode( 'endtag', 'select' );
-                                       return $this->insertToken( $token, $value, $attribs, $selfClose );
-                               case 'script':
-                               case 'template':
-                                       return $this->inHeadMode( $token, $value, $attribs, $selfClose );
-                       }
-               } elseif ( $token === 'endtag' ) {
-                       switch ( $value ) {
-                               case 'optgroup':
-                                       if (
-                                               $this->stack->currentNode->isHtmlNamed( 'option' ) &&
-                                               $this->stack->length() >= 2 &&
-                                               $this->stack->node( $this->stack->length() - 2 )->isHtmlNamed( 'optgroup' )
-                                       ) {
-                                               $this->stack->pop();
-                                       }
-                                       if ( $this->stack->currentNode->isHtmlNamed( 'optgroup' ) ) {
-                                               $this->stack->pop();
-                                       }
-                                       return true;
-                               case 'option':
-                                       if ( $this->stack->currentNode->isHtmlNamed( 'option' ) ) {
-                                               $this->stack->pop();
-                                       }
-                                       return true;
-                               case 'select':
-                                       if ( !$this->stack->inSelectScope( $value ) ) {
-                                               return true; // fragment case
-                                       }
-                                       $this->stack->popTag( $value );
-                                       $this->resetInsertionMode();
-                                       return true;
-                               case 'template':
-                                       return $this->inHeadMode( $token, $value, $attribs, $selfClose );
-                       }
-               } elseif ( $token === 'comment' ) {
-                       $this->stack->insertComment( $value );
-                       return true;
-               }
-               // anything else: just ignore the token
-               return true;
-       }
-
-       private function inSelectInTableMode( $token, $value, $attribs = null, $selfClose = false ) {
-               switch ( $value ) {
-                       case 'caption':
-                       case 'table':
-                       case 'tbody':
-                       case 'tfoot':
-                       case 'thead':
-                       case 'tr':
-                       case 'td':
-                       case 'th':
-                               if ( $token === 'tag' ) {
-                                       $this->inSelectInTableMode( 'endtag', 'select' );
-                                       return $this->insertToken( $token, $value, $attribs, $selfClose );
-                               } elseif ( $token === 'endtag' ) {
-                                       if ( $this->stack->inTableScope( $value ) ) {
-                                               $this->inSelectInTableMode( 'endtag', 'select' );
-                                               return $this->insertToken( $token, $value, $attribs, $selfClose );
-                                       }
-                                       return true;
-                               }
-               }
-               // anything else
-               return $this->inSelectMode( $token, $value, $attribs, $selfClose );
-       }
-
-       private function inTemplateMode( $token, $value, $attribs = null, $selfClose = false ) {
-               if ( $token === 'text' || $token === 'comment' ) {
-                       return $this->inBodyMode( $token, $value, $attribs, $selfClose );
-               } elseif ( $token === 'eof' ) {
-                       if ( $this->stack->indexOf( 'template' ) < 0 ) {
-                               $this->stopParsing();
-                       } else {
-                               $this->stack->popTag( 'template' );
-                               $this->afe->clearToMarker();
-                               array_pop( $this->templateInsertionModes );
-                               $this->resetInsertionMode();
-                               $this->insertToken( $token, $value, $attribs, $selfClose );
-                       }
-                       return true;
-               } elseif ( $token === 'tag' ) {
-                       switch ( $value ) {
-                               case 'base':
-                               case 'basefont':
-                               case 'bgsound':
-                               case 'link':
-                               case 'meta':
-                               case 'noframes':
-                               // OMITTED: <script>
-                               case 'style':
-                               case 'template':
-                               // OMITTED: <title>
-                                       return $this->inHeadMode( $token, $value, $attribs, $selfClose );
-
-                               case 'caption':
-                               case 'colgroup':
-                               case 'tbody':
-                               case 'tfoot':
-                               case 'thead':
-                                       return $this->switchModeAndReprocess(
-                                               'inTableMode', $token, $value, $attribs, $selfClose
-                                       );
-
-                               case 'col':
-                                       return $this->switchModeAndReprocess(
-                                               'inColumnGroupMode', $token, $value, $attribs, $selfClose
-                                       );
-
-                               case 'tr':
-                                       return $this->switchModeAndReprocess(
-                                               'inTableBodyMode', $token, $value, $attribs, $selfClose
-                                       );
-
-                               case 'td':
-                               case 'th':
-                                       return $this->switchModeAndReprocess(
-                                               'inRowMode', $token, $value, $attribs, $selfClose
-                                       );
-                       }
-                       return $this->switchModeAndReprocess(
-                               'inBodyMode', $token, $value, $attribs, $selfClose
-                       );
-               } elseif ( $token === 'endtag' ) {
-                       switch ( $value ) {
-                               case 'template':
-                                       return $this->inHeadMode( $token, $value, $attribs, $selfClose );
-                       }
-                       return true;
-               } else {
-                       Assert::invariant( false, "Bad token type: $token" );
-               }
-       }
-}
diff --git a/includes/tidy/Html5Depurate.php b/includes/tidy/Html5Depurate.php
deleted file mode 100644 (file)
index c6acd66..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-
-namespace MediaWiki\Tidy;
-
-use MWHttpRequest;
-use Exception;
-
-class Html5Depurate extends TidyDriverBase {
-       public function __construct( array $config ) {
-               parent::__construct( $config + [
-                       'url' => 'http://localhost:4339/document',
-                       'timeout' => 10,
-                       'connectTimeout' => 0.5,
-               ] );
-       }
-
-       public function tidy( $text ) {
-               $wrappedtext = '<!DOCTYPE html><html>' .
-                       '<body>' . $text . '</body></html>';
-
-               $req = MWHttpRequest::factory( $this->config['url'],
-                       [
-                               'method' => 'POST',
-                               'timeout' => $this->config['timeout'],
-                               'connectTimeout' => $this->config['connectTimeout'],
-                               'postData' => [
-                                       'text' => $wrappedtext
-                               ]
-                       ] );
-               $status = $req->execute();
-               if ( !$status->isOK() ) {
-                       throw new Exception( "Error contacting depurate service: "
-                               . $status->getWikiText( false, false, 'en' ) );
-               } elseif ( $req->getStatus() !== 200 ) {
-                       throw new Exception( "Depurate returned error: " . $status->getWikiText( false, false, 'en' ) );
-               }
-               $result = $req->getContent();
-               $startBody = strpos( $result, "<body>" );
-               $endBody = strrpos( $result, "</body>" );
-               if ( $startBody !== false && $endBody !== false && $endBody > $startBody ) {
-                       $startBody += strlen( "<body>" );
-                       return substr( $result, $startBody, $endBody - $startBody );
-               } else {
-                       return $text . "\n<!-- Html5Depurate returned an invalid result -->";
-               }
-       }
-}
diff --git a/includes/tidy/Html5Internal.php b/includes/tidy/Html5Internal.php
deleted file mode 100644 (file)
index 4ad8200..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-
-namespace MediaWiki\Tidy;
-
-class Html5Internal extends TidyDriverBase {
-       private $balancer;
-       public function __construct( array $config ) {
-               parent::__construct( $config + [
-                       'strict' => true,
-                       'tidyCompat' => true,
-               ] );
-               $this->balancer = new Balancer( $this->config );
-       }
-
-       public function tidy( $text ) {
-               return $this->balancer->balance( $text );
-       }
-}
index a3717b2..245982e 100644 (file)
@@ -2,6 +2,8 @@
 
 namespace MediaWiki\Tidy;
 
+use MWException;
+
 abstract class RaggettBase extends TidyDriverBase {
        /**
         * Generic interface for wrapping and unwrapping HTML for Dave Raggett's tidy.
index f102d49..6b8153c 100644 (file)
@@ -261,6 +261,15 @@ class BotPassword implements IDBAccessObject {
                }
        }
 
+       /**
+        * Whether the password is currently invalid
+        * @since 1.32
+        * @return bool
+        */
+       public function isInvalid() {
+               return $this->getPassword() instanceof InvalidPassword;
+       }
+
        /**
         * Save the BotPassword to the database
         * @param string $operation 'update' or 'insert'
@@ -491,7 +500,11 @@ class BotPassword implements IDBAccessObject {
                }
 
                // Check the password
-               if ( !$bp->getPassword()->equals( $password ) ) {
+               $passwordObj = $bp->getPassword();
+               if ( $passwordObj instanceof InvalidPassword ) {
+                       return Status::newFatal( 'botpasswords-needs-reset', $name, $appId );
+               }
+               if ( !$passwordObj->equals( $password ) ) {
                        return Status::newFatal( 'wrongpassword' );
                }
 
index 3335e59..5a5139d 100644 (file)
@@ -414,7 +414,8 @@ class User implements IDBAccessObject, UserIdentity {
                                break;
                        case 'actor':
                                // Make sure this thread sees its own changes
-                               if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
+                               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+                               if ( $lb->hasOrMadeRecentMasterChanges() ) {
                                        $flags |= self::READ_LATEST;
                                        $this->queryFlagsUsed = $flags;
                                }
@@ -4559,7 +4560,7 @@ class User implements IDBAccessObject, UserIdentity {
         * site.
         *
         * @param string $val Input value to compare
-        * @param string $salt Optional function-specific data for hashing
+        * @param string|array $salt Optional function-specific data for hashing
         * @param WebRequest|null $request Object to use or null to use $wgRequest
         * @param int $maxage Fail tokens older than this, in seconds
         * @return bool Whether the token matches
@@ -4573,7 +4574,7 @@ class User implements IDBAccessObject, UserIdentity {
         * ignoring the suffix.
         *
         * @param string $val Input value to compare
-        * @param string $salt Optional function-specific data for hashing
+        * @param string|array $salt Optional function-specific data for hashing
         * @param WebRequest|null $request Object to use or null to use $wgRequest
         * @param int $maxage Fail tokens older than this, in seconds
         * @return bool Whether the token matches
index 0e2ef85..98d2c0e 100644 (file)
@@ -396,6 +396,7 @@ class ClassCollector {
                        case T_INTERFACE:
                        case T_TRAIT:
                        case T_DOUBLE_COLON:
+                       case T_NEW:
                                $this->startToken = $token;
                                break;
                        case T_STRING:
@@ -418,6 +419,12 @@ class ClassCollector {
                                // "self::static" which accesses the class name. It doens't define a new class.
                                $this->startToken = null;
                                break;
+                       case T_NEW:
+                               // Skip over T_CLASS after T_NEW because this is a PHP 7 anonymous class.
+                               if ( !is_array( $token ) || $token[0] !== T_WHITESPACE ) {
+                                       $this->startToken = null;
+                               }
+                               break;
                        case T_NAMESPACE:
                                if ( $token === ';' || $token === '{' ) {
                                        $this->namespace = $this->implodeTokens() . '\\';
index a450ae5..30d1cbb 100644 (file)
@@ -19,6 +19,7 @@
  * @ingroup Watchlist
  */
 use MediaWiki\Linker\LinkTarget;
+use Wikimedia\Rdbms\DBUnexpectedError;
 
 /**
  * @author Addshore
index 01a5a79..1698b9f 100644 (file)
  */
 class CrhConverter extends LanguageConverter {
        // Defines working character ranges
-       const WORD_BEGINS = '\r\s\"\'\(\)\-<>\[\]\/.,:;!?';
-       const WORD_ENDS = '\r\s\"\'\(\)\-<>\[\]\/.,:;!?';
 
        // Cyrillic
        const C_UC = 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'; # Crimean Tatar Cyrillic uppercase
        const C_LC = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя'; # Crimean Tatar Cyrillic lowercase
        const C_CONS_UC = 'БВГДЖЗЙКЛМНПРСТФХЦЧШЩCÑ'; # Crimean Tatar Cyrillic + CÑ uppercase consonants
        const C_CONS_LC = 'бвгджзйклмнпрстфхцчшщcñ'; # Crimean Tatar Cyrillic + CÑ lowercase consonants
-       const C_M_CONS = 'бгкмпшcБГКМПШC'; # Crimean Tatar Cyrillic M-type consonants
+       const C_M_CONS = 'бгкмшcБГКМШC'; # Crimean Tatar Cyrillic M-type consonants
 
-       # Crimean Tatar Cyrillic + CÑ consonants
+       // Crimean Tatar Cyrillic + CÑ consonants
        const C_CONS = 'бвгджзйклмнпрстфхцчшщcñБВГДЖЗЙКЛМНПРСТФХЦЧШЩCÑ';
 
        // Latin
@@ -50,9 +48,9 @@ class CrhConverter extends LanguageConverter {
        const L_N_CONS_LC = 'çnrstz'; # Crimean Tatar Latin N-type lower case consonants
        const L_N_CONS = 'çnrstzÇNRSTZ'; # Crimean Tatar Latin N-type consonants
        const L_M_CONS = 'bcgkmpşBCGKMPŞ'; # Crimean Tatar Latin M-type consonants
-       const L_CONS_UC = 'BCÇDFGHJKLMNÑPRSŞTVZ'; # Crimean Tatar Latin uppercase consonants
-       const L_CONS_LC = 'bcçdfghjklmnñprsştvz'; # Crimean Tatar Latin lowercase consonants
-       const L_CONS = 'bcçdfghjklmnñprsştvzBCÇDFGHJKLMNÑPRSŞTVZ'; # Crimean Tatar Latin consonants
+       const L_CONS_UC = 'BCÇDFGĞHJKLMNÑPQRSŞTVZ'; # Crimean Tatar Latin uppercase consonants
+       const L_CONS_LC = 'bcçdfgğhjklmnñpqrsştvz'; # Crimean Tatar Latin lowercase consonants
+       const L_CONS = 'bcçdfgğhjklmnñpqrsştvzBCÇDFGĞHJKLMNÑPQRSŞTVZ'; # Crimean Tatar Latin consonants
        const L_VOW_UC = 'AÂEIİOÖUÜ'; # Crimean Tatar Latin uppercase vowels
        const L_VOW = 'aâeıioöuüAÂEIİOÖUÜ'; # Crimean Tatar Latin vowels
        const L_F_UC = 'EİÖÜ'; # Crimean Tatar Latin uppercase front vowels
@@ -133,9 +131,12 @@ class CrhConverter extends LanguageConverter {
 
                ];
 
-       public $mExceptions = [];
+       public $mCyrl2LatnExceptions = [];
+       public $mLatn2CyrlExceptions = [];
+
        public $mCyrl2LatnPatterns = [];
        public $mLatn2CyrlPatterns = [];
+
        public $mCyrlCleanUpRegexes = [];
 
        public $mExceptionsLoaded = false;
@@ -155,9 +156,9 @@ class CrhConverter extends LanguageConverter {
 
                $this->mExceptionsLoaded = true;
                $crhExceptions = new MediaWiki\Languages\Data\CrhExceptions();
-               list( $this->mExceptions, $this->mCyrl2LatnPatterns, $this->mLatn2CyrlPatterns,
-                       $this->mCyrlCleanUpRegexes ) = $crhExceptions->loadExceptions( self::L_LC . self::C_LC,
-                       self::L_UC . self::C_UC );
+               list( $this->mCyrl2LatnExceptions, $this->mLatn2CyrlExceptions,
+                       $this->mCyrl2LatnPatterns, $this->mLatn2CyrlPatterns, $this->mCyrlCleanUpRegexes ) =
+                       $crhExceptions->loadExceptions( self::L_LC . self::C_LC, self::L_UC . self::C_UC );
        }
 
        /**
@@ -197,17 +198,12 @@ class CrhConverter extends LanguageConverter {
         * @return string
         */
        function translate( $text, $toVariant ) {
-               $letters = '';
                switch ( $toVariant ) {
                        case 'crh-cyrl':
-                               $letters = self::L_UC . self::L_LC . "\'";
-                               break;
                        case 'crh-latn':
-                               $letters = self::C_UC . self::C_LC . "";
                                break;
                        default:
                                return $text;
-                               break;
                }
 
                if ( !$this->mTablesLoaded ) {
@@ -218,48 +214,41 @@ class CrhConverter extends LanguageConverter {
                        throw new MWException( "Broken variant table: " . implode( ',', array_keys( $this->mTables ) ) );
                }
 
-               // check for roman numbers like VII, XIX...
-               // Lookahead assertion ensures $roman doesn't match the empty string
-               $roman = '/^(?=[MDCLXVI])M{0,4}(C[DM]|D?C{0,3})(X[LC]|L?X{0,3})(I[VX]|V?I{0,3})$/u';
-
-               # match any sub-string of the relevant letters and convert it
-               $matches = preg_split( '/(\b|^)[^' . $letters . ']+(\b|$)/u',
-                       $text, -1, PREG_SPLIT_OFFSET_CAPTURE );
-               $mstart = 0;
-               $ret = '';
-               foreach ( $matches as $m ) {
-                       # copy over the non-matching bit
-                       $ret .= substr( $text, $mstart, $m[1] - $mstart );
-                       # skip certain classes of strings
-
-                       if ( array_key_exists( $m[0], $this->mExceptions ) ) {
-                               # if it's an exception, just copy down the right answer
-                               $ret .= $this->mExceptions[$m[0]];
-                       } elseif ( ! $m[0] || # empty strings
-                                        preg_match( $roman, $m[0] ) || # roman numerals
-                                        preg_match( '/[^' . $letters . ']/', $m[0] ) # mixed orthography
-                                       ) {
-                               $ret .= $m[0];
-                       } else {
-                               # convert according to the rules
-                               $token = $this->regsConverter( $m[0], $toVariant );
-                               $ret .= parent::translate( $token, $toVariant );
-                       }
-                       $mstart = $m[1] + strlen( $m[0] );
-               }
-
-               # pick up stray quote marks
                switch ( $toVariant ) {
                        case 'crh-cyrl':
-                               $ret = strtr( $ret, [ '“' => '«', '”' => '»', ] );
-                               $ret = $this->regsConverter( $ret, 'cyrl-cleanup' );
-                               break;
-                       case 'crh-latn':
-                               $ret = strtr( $ret, [ '«' => '"', '»' => '"', ] );
-                               break;
-               }
+                               /* Check for roman numbers like VII, XIX...
+                                * Only need to split on Roman numerals when converting to Cyrillic
+                                * Lookahead assertion ensures $roman doesn't match the empty string, and
+                                * non-period after first "Roman" character allows initials to be converted
+                                */
+                               $roman = '(?=[MDCLXVI]([^.]|$))M{0,4}(C[DM]|D?C{0,3})(X[LC]|L?X{0,3})(I[VX]|V?I{0,3})';
+
+                               $breaks = '([^\w\x80-\xff])';
+
+                               // allow for multiple Roman numerals in a row; rare but it happens
+                               $romanRegex = '/^' . $roman . '$|^(' . $roman . $breaks . ')+|(' . $breaks . $roman . ')+$|' .
+                                       $breaks . '(' . $roman . $breaks . ')+/';
+
+                               $matches = preg_split( $romanRegex, $text, -1, PREG_SPLIT_OFFSET_CAPTURE );
+                               $mstart = 0;
+                               $ret = '';
+                               foreach ( $matches as $m ) {
+                                       // copy over Roman numerals
+                                       $ret .= substr( $text, $mstart, $m[1] - $mstart );
+
+                                       // process everything else
+                                       if ( $m[0] !== '' ) {
+                                               $ret .= $this->regsConverter( $m[0], $toVariant );
+                                       }
+
+                                       $mstart = $m[1] + strlen( $m[0] );
+                               }
 
-               return $ret;
+                               return $ret;
+                       default:
+                               // Just process the whole string in one go
+                               return $this->regsConverter( $text, $toVariant );
+               }
        }
 
        private function regsConverter( $text, $toVariant ) {
@@ -269,16 +258,20 @@ class CrhConverter extends LanguageConverter {
                $rep = [];
                switch ( $toVariant ) {
                        case 'crh-latn':
+                               $text = strtr( $text, $this->mCyrl2LatnExceptions );
                                foreach ( $this->mCyrl2LatnPatterns as $pat => $rep ) {
                                        $text = preg_replace( $pat, $rep, $text );
                                }
+                               $text = parent::translate( $text, $toVariant );
+                               $text = strtr( $text, [ '«' => '"', '»' => '"', ] );
                                return $text;
                        case 'crh-cyrl':
+                               $text = strtr( $text, $this->mLatn2CyrlExceptions );
                                foreach ( $this->mLatn2CyrlPatterns as $pat => $rep ) {
                                        $text = preg_replace( $pat, $rep, $text );
                                }
-                               return $text;
-                       case 'cyrl-cleanup':
+                               $text = parent::translate( $text, $toVariant );
+                               $text = strtr( $text, [ '“' => '«', '”' => '»', ] );
                                foreach ( $this->mCyrlCleanUpRegexes as $pat => $rep ) {
                                        $text = preg_replace( $pat, $rep, $text );
                                }
index d656528..e3bb156 100644 (file)
@@ -17,7 +17,9 @@ class CrhExceptions {
                $this->loadRegs();
        }
 
-       public $exceptionMap = [];
+       public $Cyrl2LatnExceptions = [];
+       public $Latn2CyrlExceptions = [];
+
        public $Cyrl2LatnPatterns = [];
        public $Latn2CyrlPatterns = [];
 
@@ -59,10 +61,12 @@ class CrhExceptions {
        private function addMappings( $mapArray, &$A2B, &$B2A, $exactCase = false,
                        $prePat = '', $postPat = '' ) {
                foreach ( $mapArray as $WordA => $WordB ) {
-                       $ucA = $this->myUc( $WordA );
-                       $ucWordA = $this->myUcWord( $WordA );
-                       $ucB = $this->myUc( $WordB );
-                       $ucWordB = $this->myUcWord( $WordB );
+                       if ( ! $exactCase ) {
+                               $ucA = $this->myUc( $WordA );
+                               $ucWordA = $this->myUcWord( $WordA );
+                               $ucB = $this->myUc( $WordB );
+                               $ucWordB = $this->myUcWord( $WordB );
+                       }
 
                        # if there are regexes, only map toward backregs
                        if ( ! preg_match( '/\$[1-9]/', $WordA ) ) {
@@ -86,94 +90,130 @@ class CrhExceptions {
        function loadExceptions( $lcChars, $ucChars ) {
                # init lc and uc, as needed
                $this->initLcUc( $lcChars, $ucChars );
-               # load C2L and L2C whole-word exceptions into the same array, since it's just a look up
-               # no regex prefix/suffix needed
-               $this->addMappings( $this->wordMappings, $this->exceptionMap, $this->exceptionMap );
-               $this->addMappings( $this->exactCaseMappings, $this->exceptionMap, $this->exceptionMap, true );
 
-               # load C2L and L2C bidirectional prefix mappings
+               # no regex prefix/suffix needed
+               $this->addMappings( $this->ManyToOneC2LMappings,
+                       // reverse exception mapping order to handle many-to-one C2L mappings
+                       $this->Latn2CyrlExceptions, $this->Cyrl2LatnExceptions );
+               $this->addMappings( $this->multiCaseMappings,
+                       $this->Cyrl2LatnExceptions, $this->Latn2CyrlExceptions );
+               $this->addMappings( $this->exactCaseMappings,
+                       $this->Cyrl2LatnExceptions, $this->Latn2CyrlExceptions, true );
+
+               # load C2L and L2C bidirectional affix mappings
                $this->addMappings( $this->prefixMapping,
-                       $this->Cyrl2LatnPatterns, $this->Latn2CyrlPatterns, false, '/^', '/u' );
+                       $this->Cyrl2LatnPatterns, $this->Latn2CyrlPatterns, false, '/\b', '/u' );
                $this->addMappings( $this->suffixMapping,
-                       $this->Cyrl2LatnPatterns, $this->Latn2CyrlPatterns, false, '/', '$/u' );
+                       $this->Cyrl2LatnPatterns, $this->Latn2CyrlPatterns, false, '/', '\b/u' );
 
                # tack on one-way mappings to the ends of the prefix and suffix patterns
                $this->Cyrl2LatnPatterns += $this->Cyrl2LatnRegexes;
                $this->Latn2CyrlPatterns += $this->Latn2CyrlRegexes;
 
-               return [ $this->exceptionMap, $this->Cyrl2LatnPatterns,
+               return [ $this->Cyrl2LatnExceptions, $this->Latn2CyrlExceptions, $this->Cyrl2LatnPatterns,
                        $this->Latn2CyrlPatterns, $this->CyrlCleanUpRegexes ];
        }
 
-       # map Cyrillic to Latin and back, whole word match only
+       # map Latin to Cyrillic and back, simple string match only (no regex)
        # variants: all lowercase, all uppercase, first letter capitalized
-       # items with capture group refs (e.g., $1) are only mapped from the
-       # regex to the reference
-       private $wordMappings = [
+       private $ManyToOneC2LMappings = [
+               # Carefully ordered many-to-one mappings
+               # these are ordered so C2L is correct (the later Latin one)
+               # see also L2C mappings below
+               'fevqülade' => 'февкъульаде', 'fevqulade' => 'февкъульаде',
+               'beyude' => 'бейуде', 'beyüde' => 'бейуде',
+               'curat' => 'джурьат', 'cürat' => 'джурьат',
+               'mesul' => 'месуль', 'mesül' => 'месуль',
+       ];
+
+       # map Cyrillic to Latin and back, simple string match only (no regex)
+       # variants: all lowercase, all uppercase, first letter capitalized
+       private $multiCaseMappings = [
 
-               #### originally Cyrillic to Latin
+               #### Cyrillic to Latin
                'аджыумер' => 'acıümer', 'аджыусеин' => 'acıüsein', 'алейкум' => 'aleyküm',
-               'бейуде' => 'beyüde', 'боливия' => 'boliviya', 'большевик' => 'bolşevik', 'борис' => 'boris',
-               'борнен' => 'bornen', 'бугун' => 'bugün', 'бузкесен' => 'buzkesen', 'буксир' => 'buksir',
-               'бульбуль' => 'bülbül', 'бульвар' => 'bulvar', 'бульдозер' => 'buldozer', 'бульон' => 'bulyon',
+               'бозтюс' => 'boztüs', 'боливия' => 'boliviya', 'большевик' => 'bolşevik', 'борис' => 'boris',
+               'борнен' => 'bornen', 'бублик' => 'bublik', 'буддизм' => 'buddizm', 'буддист' => 'buddist',
+               'буженина' => 'bujenina', 'бузкесен' => 'buzkesen', 'букинист' => 'bukinist',
+               'буксир' => 'buksir', 'бульбул' => 'bülbül', 'бульвар' => 'bulvar', 'бульдог' => 'buldog',
+               'бульдозер' => 'buldozer', 'бульон' => 'bulyon', 'бумеранг' => 'bumerang',
                'бунен' => 'bunen', 'буннен' => 'bunnen', 'бус-бутюн' => 'büs-bütün',
-               'бутерброд' => 'buterbrod', 'буфер' => 'bufer', 'буфет' => 'bufet', 'гонъюл' => 'göñül',
-               'горизонт' => 'gorizont', 'госпиталь' => 'gospital', 'гуливер' => 'guliver', 'гуна' => 'güna',
-               'гунях' => 'günâh', 'гургуль' => 'gürgül', 'гуя' => 'güya', 'демирёл' => 'demiryol',
-               'джуньджу' => 'cüncü', 'ёлнен' => 'yolnen', 'зумбуль' => 'zümbül', 'ильи' => 'ilyi', 'ишунь' =>
-               'işün', 'кодекс' => 'kodeks', 'кодифик' => 'kodifik', 'койлю' => 'köylü', 'коккоз' =>
-               'kökköz', 'коккозь' => 'kökköz', 'коккозю' => 'kökközü', 'кокос' => 'kokos',
-               'коллег' => 'kolleg', 'коллект' => 'kollekt', 'коллекц' => 'kollekts', 'кольцов' => 'koltsov',
-               'комбин' => 'kombin', 'комедия' => 'komediya', 'коменда' => 'komenda', 'комета' => 'kometa',
-               'комис' => 'komis', 'комит' => 'komit', 'комите' => 'komite', 'коммент' => 'komment',
-               'коммерс' => 'kommers', 'коммерц' => 'kommerts', 'компенс' => 'kompens', 'компил' => 'kompil',
-               'компьютер' => 'kompyuter', 'конвейер' => 'konveyer', 'конвен' => 'konven',
-               'конверт' => 'konvert', 'конденс' => 'kondens', 'кондитер' => 'konditer',
-               'кондиц' => 'kondits', 'коник' => 'konik', 'консерв' => 'konserv', 'контейнер' => 'konteyner',
-               'континент' => 'kontinent', 'конфе' => 'konfe', 'конфискац' => 'konfiskats',
-               'концен' => 'kontsen', 'концерт' => 'kontsert', 'конъюктур' => 'konyuktur',
-               'коньки' => 'konki', 'коньяк' => 'konyak', 'копирле' => 'kopirle', 'копия' => 'kopiya',
-               'корбекул' => 'körbekül', 'кореиз' => 'koreiz', 'коренн' => 'korenn', 'корея' => 'koreya',
-               'коридор' => 'koridor', 'корнеев' => 'korneyev', 'корре' => 'korre', 'корьбекул' =>
-               'körbekül', 'косме' => 'kosme', 'космик' => 'kosmik', 'костюм' => 'kostüm', 'котельн' =>
-               'koteln', 'котировка' => 'kotirovka', 'котлет' => 'kotlet', 'кочергин' => 'koçergin',
-               'коше' => 'köşe', 'кудрин' => 'kudrin', 'кузнец' => 'kuznets', 'кулинар' => 'kulinar',
-               'кулич' => 'kuliç', 'кульминац' => 'kulminats', 'культив' => 'kultiv',
-               'культура' => 'kultura', 'куркулет' => 'kürkület', 'курсив' => 'kursiv', 'кушку' => 'küşkü',
-               'куюк' => 'küyük', 'къарагоз' => 'qaragöz', 'къолязма' => 'qolyazma', 'къуртумер' =>
-               'qurtümer', 'къуртусеин' => 'qurtüsein', 'марьино' => 'maryino', 'медьюн' => 'medyun',
-               'месули' => 'mesüli', 'месуль' => 'mesül', 'мефкуре' => 'mefküre', 'могедек' => 'mögedek',
-               'муур' => 'müür', 'муче' => 'müçe', 'муюз' => 'müyüz', 'огнево' => 'ognevo',
-               'одеколон' => 'odekolon', 'одеса' => 'odesa', 'одесса' => 'odessa', 'озерки' => 'ozerki',
-               'озерн' => 'ozern', 'озёрн' => 'ozörn', 'океан' => 'okean', 'оленев' => 'olenev',
-               'олимп' => 'olimp', 'ольчер' => 'ölçer', 'онен' => 'onen', 'оннен' => 'onnen',
-               'опера' => 'opera', 'оптим' => 'optim', 'опци' => 'optsi', 'опция' => 'optsiya',
-               'орден' => 'orden', 'ордер' => 'order', 'ореанда' => 'oreanda', 'орех' => 'oreh',
-               'оригинал' => 'original', 'ориент' => 'oriyent', 'оркестр' => 'orkestr', 'орлин' => 'orlin',
-               'офис' => 'ofis', 'офицер' => 'ofitser', 'офсет' => 'ofset', 'оюннен' => 'oyunnen', 'побед' =>
-               'pobed', 'полево' => 'polevo', 'поли' => 'poli', 'полюшко' => 'polüşko',
-               'помидор' => 'pomidor', 'пониз' => 'poniz', 'порфир' => 'porfir', 'потелов' => 'potelov',
-               'почетн' => 'poçetn', 'почётн' => 'poçötn', 'публик' => 'publik', 'публиц' => 'publits',
-               'пушкин' => 'puşkin', 'сеитумер' => 'seitümer', 'сеитусеин' => 'seitüsein', 'сеитягъя' =>
-               'seityağya', 'сеитягья' => 'seityagya', 'сеитяхья' => 'seityahya', 'сеитяя' => 'seityaya',
+               'бутерброд' => 'buterbrod', 'бутилен' => 'butilen', 'бутилир' => 'butilir',
+               'буфер' => 'bufer', 'буфет' => 'bufet', 'гобелен' => 'gobelen', 'гомео' => 'gomeo',
+               'горизонт' => 'gorizont', 'госпитал' => 'gospital', 'готтентот' => 'gottentot',
+               'гофрир' => 'gofrir', 'губерн' => 'gubern', 'гуверн' => 'guvern', 'гугенот' => 'gugenot',
+               'гуливер' => 'guliver', 'гуна' => 'güna', 'гунях' => 'günâh', 'гургуль' => 'gürgül',
+               'гуя' => 'güya', 'дёрткуль' => 'dörtkül', 'джуньджу' => 'cüncü', 'ёлнен' => 'yolnen',
+               'зумбуль' => 'zümbül', 'ильи' => 'ilyi', 'ишунь' => 'işün', 'ковер' => 'kover', 'код' => 'kod',
+               'койлю' => 'köylü', 'кокагъач' => 'kökağaç', 'кокбаштанкъара' => 'kökbaştanqara',
+               'кокгогерджин' => 'kökgögercin', 'кокдогъан' => 'kökdoğan', 'коккозю' => 'kökközü',
+               'коккъузгъун' => 'kökquzğun', 'коклюш' => 'koklüş', 'кокташ' => 'köktaş',
+               'коктогъан' => 'köktoğan', 'коктотай' => 'köktotay', 'коллег' => 'kolleg',
+               'коллект' => 'kollekt', 'коллекц' => 'kollekts', 'колье' => 'kolye', 'кольраби' => 'kolrabi',
+               'кольцов' => 'koltsov', 'комби' => 'kombi', 'комеди' => 'komedi', 'коменда' => 'komenda',
+               'комета' => 'kometa', 'комив' => 'komiv', 'комис' => 'komis', 'комит' => 'komit',
+               'комм' => 'komm', 'коммент' => 'komment', 'коммерс' => 'kommers', 'коммерц' => 'kommerts',
+               'комп' => 'komp', 'конве' => 'konve', 'конгени' => 'kongeni', 'конденс' => 'kondens',
+               'кондил' => 'kondil', 'кондитер' => 'konditer', 'кондиц' => 'kondits', 'коник' => 'konik',
+               'конкис' => 'konkis', 'консерв' => 'konserv', 'конси' => 'konsi', 'контейнер' => 'konteyner',
+               'конти' => 'konti', 'конфе' => 'konfe', 'конфи' => 'konfi', 'конце' => 'kontse',
+               'конъю' => 'konyu', 'коньки' => 'konki', 'коньяк' => 'konyak', 'копирле' => 'kopirle',
+               'копия' => 'kopiya', 'корде' => 'korde', 'кореиз' => 'koreiz', 'коренн' => 'korenn',
+               'корея' => 'koreya', 'кориа' => 'koria', 'коридор' => 'koridor', 'корне' => 'korne',
+               'корнеев' => 'korneyev', 'корни' => 'korni', 'корре' => 'korre', 'косме' => 'kosme',
+               'космик' => 'kosmik', 'костюм' => 'kostüm', 'котельн' => 'koteln', 'котир' => 'kotir',
+               'котлет' => 'kotlet', 'кочерг' => 'koçerg', 'коше' => 'köşe', 'куби' => 'kubi',
+               'кудрин' => 'kudrin', 'кузнец' => 'kuznets', 'кулинар' => 'kulinar', 'кулич' => 'kuliç',
+               'кульмин' => 'kulmin', 'культаш' => 'kültaş', 'культе' => 'külte', 'культ' => 'kult',
+               'куркулет' => 'kürkület', 'курсив' => 'kursiv', 'кушет' => 'kuşet', 'кушку' => 'küşkü',
+               'куюк' => 'küyük', 'къолязма' => 'qolyazma', 'къуртумер' => 'qurtümer',
+               'къуртусеин' => 'qurtüsein', 'медьюн' => 'medyun', 'месули' => 'mesüli',
+               'мефкуре' => 'mefküre', 'могедек' => 'mögedek', 'мумиё' => 'mumiyo', 'мумиф' => 'mumif',
+               'муче' => 'müçe', 'муюз' => 'müyüz', 'нумюне' => 'nümüne', 'обел' => 'obel', 'обер' => 'ober',
+               'обли' => 'obli', 'обсе' => 'obse', 'обт' => 'obt', 'огне' => 'ogne', 'одеколон' => 'odekolon',
+               'одеса' => 'odesa', 'одесса' => 'odessa', 'озерки' => 'ozerki', 'озерн' => 'ozern',
+               'озёрн' => 'ozörn', 'озюя' => 'özüya', 'океан' => 'okean', 'окси' => 'oksi',
+               'октет' => 'oktet', 'олеа' => 'olea', 'олеи' => 'olei', 'оленев' => 'olenev', 'олив' => 'oliv',
+               'олиг' => 'olig', 'олимп' => 'olimp', 'олиф' => 'olif', 'ольчер' => 'ölçer', 'омле' => 'omle',
+               'онен' => 'onen', 'оннен' => 'onnen', 'опера' => 'opera', 'опере' => 'opere',
+               'оптим' => 'optim', 'опци' => 'optsi', 'орби' => 'orbi', 'орден' => 'orden',
+               'ордер' => 'order', 'ордин' => 'ordin', 'ореа' => 'orea', 'орех' => 'oreh',
+               'ориент' => 'oriyent', 'оркестр' => 'orkestr', 'орлин' => 'orlin', 'орни' => 'orni',
+               'орхи' => 'orhi', 'осци' => 'ostsi', 'офис' => 'ofis', 'офиц' => 'ofits', 'офсет' => 'ofset',
+               'очерк' => 'oçerk', 'оюннен' => 'oyunnen', 'побед' => 'pobed', 'полево' => 'polevo',
+               'поли' => 'poli', 'полюшко' => 'polüşko', 'помидор' => 'pomidor', 'пониз' => 'poniz',
+               'порфир' => 'porfir', 'потелов' => 'potelov', 'потюк' => 'pötük', 'почетн' => 'poçetn',
+               'почётн' => 'poçötn', 'пукле' => 'pükle', 'пуркю' => 'pürkü', 'пурумют' => 'purümüt',
+               'пускул' => 'püskül', 'пускур' => 'püskür', 'пусюр' => 'püsür', 'пуфле' => 'püfle',
                'сейитумер' => 'seyitümer', 'сейитусеин' => 'seyitüsein', 'сейитягъя' => 'seyityağya',
                'сейитягья' => 'seyityagya', 'сейитяхья' => 'seyityahya', 'сейитяя' => 'seyityaya',
-               'ультимат' => 'ultimat', 'ультра' => 'ultra', 'ульянов' => 'ulyanov', 'универ' => 'univer',
-               'уника' => 'unika', 'унтер' => 'unter', 'урьян' => 'uryan', 'уткин' => 'utkin', 'учебн' =>
-               'uçebn', 'шовини' => 'şovini', 'шоссе' => 'şosse', 'шубин' => 'şubin', 'шунен' => 'şunen',
-               'шуннен' => 'şunnen', 'щёлкино' => 'şçolkino', 'эмирусеин' => 'emirüsein',
-               'юзбашы' => 'yüzbaşı', 'юзйыл' => 'yüzyıl', 'юртер' => 'yurter', 'ющенко' => 'yuşçenko',
-
-               'кою' => 'köyü', 'кок' => 'kök', 'ком-кок' => 'köm-kök', 'коп' => 'köp', 'ог' => 'ög',
-               'юрип' => 'yürip', 'юз' => 'yüz', 'юк' => 'yük', 'буюп' => 'büyüp', 'буюк' => 'büyük',
-               'джонк' => 'cönk', 'джонкю' => 'cönkü', 'устке' => 'üstke', 'устте' => 'üstte',
-               'усттен' => 'üstten',
-
-               # шофёр needs to come after шофер to override it in the Latin-to-Cyrillic direction
-               'шофер' => 'şoför',
-               'шофёр' => 'şoför',
-
-               #### originally Latin to Cyrillic (deduped from above)
+               'сеитумер' => 'seitümer', 'сеитусеин' => 'seitüsein', 'сеитягъя' => 'seityağya',
+               'сеитягья' => 'seityagya', 'сеитяхья' => 'seityahya', 'сеитяя' => 'seityaya',
+               'сурет' => 'süret', 'увертюра' => 'uvertüra', 'угле' => 'ugle', 'узвий' => 'uzviy',
+               'улица' => 'ulitsa', 'ультимат' => 'ultimat', 'ультра' => 'ultra', 'ульянов' => 'ulyanov',
+               'универ' => 'univer', 'уник' => 'unik', 'унис' => 'unis', 'унит' => 'unit', 'униф' => 'unif',
+               'унтер' => 'unter', 'урьян' => 'uryan', 'утил' => 'util', 'уткин' => 'utkin',
+               'учебн' => 'uçebn', 'шовини' => 'şovini', 'шоссе' => 'şosse', 'шубин' => 'şubin',
+               'шунен' => 'şunen', 'шуннен' => 'şunnen', 'шунчюн' => 'şunçün', 'щёлкино' => 'şçolkino',
+               'эмирусеин' => 'emirüsein', 'юзбашы' => 'yüzbaşı', 'юзйыл' => 'yüzyıl', 'юртер' => 'yurter',
+               'ющенко' => 'yuşçenko',
+
+               ### Carefully ordered many-to-one mappings
+               # these are ordered so L2C is correct (the later Cyrillic one)
+               # see also $ManyToOneC2LMappings above for C2L
+               'шофер' => 'şoför', 'шофёр' => 'şoför',
+               'бугун' => 'bugün', 'бугунь' => 'bugün',
+               'демирёл' => 'demiryol', 'демиръёл' => 'demiryol',
+               'гонъюл' => 'göñül', 'гонъюль' => 'göñül',
+               'коккоз' => 'kökköz', 'коккозь' => 'kökköz',
+               'корбекул' => 'körbekül', 'корьбекул' => 'körbekül', 'корьбекуль' => 'körbekül',
+               'муур' => 'müür', 'муурь' => 'müür',
+               'оригинал' => 'original', 'оригиналь' => 'original',
+               'пускю' => 'püskü', 'пуськю' => 'püskü',
+               'къарагоз' => 'qaragöz', 'къарагозь' => 'qaragöz',
+               'етсин' => 'yetsin', 'етсин' => 'etsin',
+
+               #### Latin to Cyrillic (deduped from above)
 
                # слова на -аль
                # words in -аль
@@ -184,42 +224,39 @@ class CrhExceptions {
                'истикъбаль' => 'istiqbal', 'истикъляль' => 'istiqlâl', 'италия' => 'italiya',
                'италья' => 'italya', 'ишгъаль' => 'işğal', 'кафедраль' => 'kafedral', 'казуаль' => 'kazual',
                'коллегиаль' => 'kollegial', 'колоссаль' => 'kolossal', 'коммуналь' => 'kommunal',
-               'кординаль' => 'kordinal', 'криминаль' => 'kriminal', 'легаль' => 'legal', 'леталь' => 'letal',
-               'либеÑ\80алÑ\8c' => 'liberal', 'локалÑ\8c' => 'lokal', 'магиÑ\81Ñ\82Ñ\80алÑ\8c' => 'magistral',
-               'материаль' => 'material', 'машиналь' => 'maşinal', 'меаль' => 'meal',
-               'медалÑ\8cон' => 'medalyon', 'медалÑ\8c' => 'medal', 'меÑ\80идионалÑ\8c' => 'meridional',
-               'меÑ\88Ñ\8aалÑ\8c' => 'meÅ\9fal', 'минеÑ\80алÑ\8c' => 'mineral', 'минималÑ\8c' => 'minimal', 'миÑ\81алÑ\8c' => 'misal',
-               'модалÑ\8c' => 'modal', 'мÑ\83зÑ\8bкалÑ\8c' => 'muzıkal', 'номиналÑ\8c' => 'nominal', 'ноÑ\80малÑ\8c' => 'normal',
-               'опÑ\82ималÑ\8c' => 'optimal', 'оÑ\80биÑ\82алÑ\8c' => 'orbital', 'оÑ\80игиналÑ\8c' => 'original',
-               'педалÑ\8c' => 'pedal', 'пÑ\80опоÑ\80Ñ\86ионалÑ\8c' => 'proportsional', 'пÑ\80оÑ\84еÑ\81Ñ\81ионалÑ\8c' => 'professional',
-               'радикаль' => 'radikal', 'рациональ' => 'ratsional', 'реаль' => 'real',
-               'региональ' => 'regional', 'суаль' => 'sual', 'шималь' => 'şimal',
+               'кординаль' => 'kordinal', 'криминаль' => 'kriminal', 'легаль' => 'legal',
+               'леÑ\82алÑ\8c' => 'letal', 'либеÑ\80алÑ\8c' => 'liberal', 'локалÑ\8c' => 'lokal',
+               'магистраль' => 'magistral', 'материаль' => 'material', 'машиналь' => 'maşinal',
+               'меалÑ\8c' => 'meal', 'медалÑ\8cон' => 'medalyon', 'медалÑ\8c' => 'medal',
+               'меÑ\80идионалÑ\8c' => 'meridional', 'меÑ\88Ñ\8aалÑ\8c' => 'meÅ\9fal', 'минеÑ\80алÑ\8c' => 'mineral',
+               'минималÑ\8c' => 'minimal', 'миÑ\81алÑ\8c' => 'misal', 'модалÑ\8c' => 'modal', 'мÑ\83зÑ\8bкалÑ\8c' => 'muzıkal',
+               'номиналÑ\8c' => 'nominal', 'ноÑ\80малÑ\8c' => 'normal', 'опÑ\82ималÑ\8c' => 'optimal',
+               'оÑ\80биÑ\82алÑ\8c' => 'orbital', 'педалÑ\8c' => 'pedal', 'пÑ\80опоÑ\80Ñ\86ионалÑ\8c' => 'proportsional',
+               'профессиональ' => 'professional', 'радикаль' => 'radikal', 'рациональ' => 'ratsional',
+               'Ñ\80еалÑ\8c' => 'real', 'Ñ\80егионалÑ\8c' => 'regional', 'Ñ\81Ñ\83алÑ\8c' => 'sual', 'Ñ\88ималÑ\8c' => 'Å\9fimal',
                'территориаль' => 'territorial', 'тимсаль' => 'timsal', 'тоталь' => 'total',
                'уникаль' => 'unikal', 'универсаль' => 'universal', 'вертикаль' => 'vertikal',
                'виртуаль' => 'virtual', 'визуаль' => 'vizual', 'вуаль' => 'vual', 'зональ' => 'zonal',
-               'зуаль' => 'zual',
+               'зуаль' => 'zual', 'италь' => 'ital',
 
                # слова с мягким знаком перед а, о, у, э
                # Words with a soft sign before а, о, у, э
-               'бильакис' => 'bilakis', 'маальэсеф' => 'maalesef',
-               'мельун' => 'melun', 'озьара' => 'özara', 'вельасыл' => 'velasıl',
-               'ельаякъ' => 'yelayaq',
-               # these are ordered so C2L is correct (the later Latin one)
-               'февкъульаде' => 'fevqülade','февкъульаде' => 'fevqulade',
+               'бильакис' => 'bilakis', 'маальэсеф' => 'maalesef', 'мельун' => 'melun', 'озьара' => 'özara',
+               'вельасыл' => 'velasıl', 'ельаякъ' => 'yelayaq',
 
                # другие слова с мягким знаком
                # Other words with a soft sign
                'альбатрос' => 'albatros', 'альбинос' => 'albinos', 'альбом' => 'albom',
                'альбумин' => 'albumin', 'алфавит' => 'alfavit', 'альфа' => 'alfa', 'альманах' => 'almanah',
-               'альпинист' => 'alpinist', 'альтерн' => 'altern', 'альтру' => 'altru', 'альвеола' => 'alveola',
-               'анÑ\81амблÑ\8c' => 'ansambl', 'анÑ\8cане' => 'anane', 'аÑ\81Ñ\84алÑ\8cÑ\82' => 'asfalt', 'балÑ\8cнео' => 'balneo',
-               'бааÑ\80Ñ\8c' => 'baar', 'базалÑ\8cÑ\82' => 'bazalt', 'биноклÑ\8c' => 'binokl', 'джÑ\83Ñ\80Ñ\8cаÑ\82' => 'curat',
-               'джÑ\83Ñ\80Ñ\8cаÑ\82' => 'cürat', 'девалÑ\8cв' => 'devalv', 'Ñ\84акÑ\83лÑ\8cÑ\82' => 'fakult', 'Ñ\84алÑ\8cÑ\81иÑ\84' => 'falsif',
-               'фольклор' => 'folklor', 'гальван' => 'galvan', 'геральд' => 'gerald', 'женьшень' => 'jenşen',
+               'альпинист' => 'alpinist', 'альтерн' => 'altern', 'альтру' => 'altru',
+               'алÑ\8cвеола' => 'alveola', 'анÑ\81амблÑ\8c' => 'ansambl', 'анÑ\8cане' => 'anane', 'аÑ\81Ñ\84алÑ\8cÑ\82' => 'asfalt',
+               'балÑ\8cнео' => 'balneo', 'бааÑ\80Ñ\8c' => 'baar', 'базалÑ\8cÑ\82' => 'bazalt', 'биноклÑ\8c' => 'binokl',
+               'девалÑ\8cв' => 'devalv', 'Ñ\84акÑ\83лÑ\8cÑ\82' => 'fakult', 'Ñ\84алÑ\8cÑ\81иÑ\84' => 'falsif', 'Ñ\84олÑ\8cклоÑ\80' => 'folklor',
+               'гальван' => 'galvan', 'геральд' => 'gerald', 'женьшень' => 'jenşen',
                'инвентарь' => 'inventar', 'кальк' => 'kalk', 'кальмар' => 'kalmar', 'консульт' => 'konsult',
-               'контроль' => 'kontrol', 'кульмин' => 'kulmin', 'культур' => 'kultur', 'лагерь' => 'lager',
-               'макъбуль' => 'maqbul', 'макъуль' => 'maqul', 'мальт' => 'malt', 'мальземе' => 'malzeme',
-               'меджуль' => 'mecul', 'мешгуль' => 'meşgül', 'мешгъуль' => 'meşğul', 'мульти' => 'multi',
+               'контроль' => 'kontrol', 'культур' => 'kultur', 'лагерь' => 'lager', 'макъбуль' => 'maqbul',
+               'макъуль' => 'maqul', 'мальт' => 'malt', 'мальземе' => 'malzeme', 'меджуль' => 'mecul',
+               'мешгуль' => 'meşgül', 'мешгъуль' => 'meşğul', 'мульти' => 'multi',
                'мусульман' => 'musulman', 'нефть' => 'neft', 'пальто' => 'palto', 'пароль' => 'parol',
                'патруль' => 'patrul', 'пенальти' => 'penalti', 'къальби' => 'qalbi', 'къальпке' => 'qalpke',
                'къальплер' => 'qalpler', 'къальпни' => 'qalpni', 'къальпте' => 'qalpte', 'къаарь' => 'qaar',
@@ -233,23 +270,23 @@ class CrhExceptions {
                # слова с твёрдым знаком
                # Words with a solid sign
                'бидъат' => 'bidat', 'бузъюрек' => 'buzyürek', 'атешъюрек' => 'ateşyürek',
-               'алÑ\8aÑ\8fнакÑ\8a' => 'alyanaq', 'демиÑ\80Ñ\8aÑ\91л' => 'demiryol', 'деÑ\80Ñ\8aал' => 'deral', 'инÑ\8aекÑ\86' => 'inyekts',
-               'меÑ\84Ñ\8aÑ\83м' => 'mefum', 'меÑ\88Ñ\8aÑ\83м' => 'meÅ\9fum', 'обÑ\8aекÑ\82' => 'obyekt', 'Ñ\80азÑ\8aезд' => 'razyezd',
-               'Ñ\81Ñ\83бÑ\8aекÑ\82' => 'subyekt', 'Ñ\85авÑ\8aÑ\8fÑ\80' => 'havyar', 'Ñ\8fмÑ\8aÑ\8fм' => 'yamyam',
+               'алÑ\8aÑ\8fнакÑ\8a' => 'alyanaq', 'инÑ\8aекÑ\86' => 'inyekts', 'меÑ\84Ñ\8aÑ\83м' => 'mefum', 'меÑ\88Ñ\8aÑ\83м' => 'meÅ\9fum',
+               'обÑ\8aекÑ\82' => 'obyekt', 'Ñ\80азÑ\8aезд' => 'razyezd', 'Ñ\81Ñ\83бÑ\8aекÑ\82' => 'subyekt', 'Ñ\85авÑ\8aÑ\8fÑ\80' => 'havyar',
+               'ямъям' => 'yamyam',
 
                # слова с буквой щ
                # words with щ
                'ящик' => 'yaşçik', 'мещан' => 'meşçan',
 
-               # слова с буквой ц
+               # слова с ц
                # words with ц
                'акциз' => 'aktsiz', 'ацет' => 'atset', 'блиц' => 'blits', 'бруцеллёз' => 'brutsellöz',
                'доцент' => 'dotsent', 'фармацевт' => 'farmatsevt', 'глицер' => 'glitser',
                'люцерна' => 'lütserna', 'лицей' => 'litsey', 'меццо' => 'metstso', 'наци' => 'natsi',
                'проце' => 'protse', 'рецеп' => 'retsep', 'реценз' => 'retsenz', 'теплица' => 'teplitsa',
-               'виÑ\86е' => 'vitse', 'Ñ\86епÑ\81' => 'tseps', 'Ñ\88вейÑ\86аÑ\80' => 'Å\9fveytsar',
+               'вице' => 'vitse', 'швейцар' => 'şveytsar',
 
-               # слова без буквы тс
+               # слова с тс
                # words with тс
                'агъартс' => 'ağarts', 'агъыртс' => 'ağırts', 'бильдиртс' => 'bildirts', 'битсин' => 'bitsin',
                'буюльтс' => 'büyülts', 'буютс' => 'büyüts', 'гебертс' => 'geberts', 'делиртс' => 'delirts',
@@ -259,253 +296,55 @@ class CrhExceptions {
                'кучертс' => 'küçerts', 'кучюльтс' => 'küçülts', 'пертсин' => 'pertsin', 'къайтс' => 'qayts',
                'къутсуз' => 'qutsuz', 'орьтс' => 'örts', 'отьс' => 'öts', 'тартс' => 'tarts',
                'тутсун' => 'tutsun', 'тюнъюльтс' => 'tüñülts', 'тюртс' => 'türts', 'янъартс' => 'yañarts',
-               'ебеÑ\80Ñ\82Ñ\81' => 'yeberts', 'еÑ\82Ñ\81ин' => 'yetsin', 'еÑ\88еÑ\80Ñ\82Ñ\81' => 'yeÅ\9ferts', 'йиÑ\80иÑ\82Ñ\81' => 'yirits',
+               'ебертс' => 'yeberts', 'ешертс' => 'yeşerts', 'йиритс' => 'yirits',
 
                # разные исключения
                # different exceptions
-               'бейуде' => 'beyude', 'бугунь' => 'bugün', 'бюджет' => 'bücet', 'бюллет' => 'büllet',
-               'бюро' => 'büro', 'бюст' => 'büst', 'джонк' => 'cönk', 'диалог' => 'dialog',
-               'гонъюль' => 'göñül', 'ханымэфенди' => 'hanımefendi', 'каньон' => 'kanyon', 'кирил' => 'kiril',
-               'кирил' => 'kirill', 'кёрджа' => 'körca', 'кой' => 'köy', 'кулеръюзь' => 'küleryüz',
-               'маалле' => 'маальle', 'майор' => 'mayor', 'маниал' => 'manиаль', 'мефкуре' => 'mefküre',
-               'месуль' => 'mesul', 'месуль' => 'mesül', 'муурь' => 'müür',
-               'нормала' => 'нормальa', 'нумюне' => 'nümüne', 'проект' => 'proekt', 'район' => 'rayon',
-               'сойады' => 'soyadı', 'спортсмен' => 'sportsmen', 'услюп' => 'üslüp', 'услюб' => 'üslüb',
-               'вакъиал' => 'vaqиаль', 'юзйыллыкъ' => 'yüzyıllıq',
+               'бюджет' => 'bücet', 'бюллет' => 'büllet', 'бюро' => 'büro', 'бюст' => 'büst',
+               'диалог' => 'dialog', 'ханымэфенди' => 'hanımefendi', 'каньон' => 'kanyon',
+               'кирил' => 'kiril', 'кирилл' => 'kirill', 'кёрджа' => 'körca', 'коy' => 'köy',
+               'кулеръюзь' => 'küleryüz', 'маалле' => 'маальle', 'майор' => 'mayor', 'маниал' => 'manиаль',
+               'нормала' => 'нормальa', 'проект' => 'proekt', 'район' => 'rayon', 'сойады' => 'soyadı',
+               'спортсмен' => 'sportsmen', 'услюп' => 'üslüp', 'услюб' => 'üslüb', 'вакъиал' => 'vaqиаль',
+               'юзйыллыкъ' => 'yüzyıllıq', 'койот' => 'koyot',
 
                # имена собственные
                # proper names
-               'адольф' => 'adolf', 'альберт' => 'albert', 'бешуй' => 'beşüy', 'эмирусеин' => 'emirüsein',
-               'флотск' => 'flotsk', 'гайана' => 'gayana', 'грэсовский' => 'gresovskiy', 'гриц' => 'grits',
-               'гурджи' => 'gürci', 'игорь' => 'igor', 'ильич' => 'ilyiç', 'ильин' => 'ilyin',
-               'исмаил' => 'ismail', 'киттс' => 'kitts', 'комсомольск' => 'komsomolsk',
-               'корьбекулю' => 'körbekülü', 'корьбекуль' => 'körbekül', 'куницын' => 'kunitsın',
-               'львив' => 'lviv', 'львов' => 'lvov', 'марьино' => 'maryino', 'махульдюр' => 'mahuldür',
-               'павел' => 'pavel', 'пантикапейон' => 'pantikapeyon', 'къарагозь' => 'qaragöz',
-               'къуртсейит' => 'qurtseyit', 'къуртсеит' => 'qurtseit', 'къуртумер' => 'qurtümer',
-               'сейитумер' => 'seyitümer', 'сеитумер' => 'seitümer', 'смаил' => 'smail',
-               'советск' => 'sovetsk', 'шемьи-заде' => 'şemi-zade', 'щёлкино' => 'şçolkino',
-               'тсвана' => 'tsvana', 'учьэвли' => 'üçevli', 'йохан' => 'yohan', 'йорк' => 'york',
-               'ющенко' => 'yuşçenko', 'льная' => 'lnaya', 'льное' => 'lnoye', 'льный' => 'lnıy',
-               'льская' => 'lskaya', 'льский' => 'lskiy', 'льское' => 'lskoye', 'ополь' => 'opol',
+               'адольф' => 'adolf', 'альберт' => 'albert', 'бешуй' => 'beşüy', 'флотск' => 'flotsk',
+               'гайана' => 'gayana', 'грэсовский' => 'gresovskiy', 'гриц' => 'grits', 'гурджи' => 'gürci',
+               'игорь' => 'igor', 'ильич' => 'ilyiç', 'ильин' => 'ilyin', 'исмаил' => 'ismail',
+               'киттс' => 'kitts', 'комсомольск' => 'komsomolsk', 'корьбекулю' => 'körbekülü',
+               'куницын' => 'kunitsın', 'львив' => 'lviv', 'львов' => 'lvov', 'марьино' => 'maryino',
+               'махульдюр' => 'mahuldür', 'павел' => 'pavel', 'пантикапейон' => 'pantikapeyon',
+               'къуртсейит' => 'qurtseyit', 'къуртсеит' => 'qurtseit', 'смаил' => 'smail',
+               'советск' => 'sovetsk', 'шемьи-заде' => 'şemi-zade', 'тсвана' => 'tsvana',
+               'учьэвли' => 'üçevli', 'йохан' => 'yohan', 'йорк' => 'york', 'винныця' => 'vinnıtsâ',
+               'винница' => 'vinnitsa', 'хмельницк' => 'hmelnitsk', 'хмельныцк' => 'hmelnıtsk',
+               'зайце' => 'zaytse', 'чистеньк' => 'çistenk', 'кольчуг' => 'kolçug', 'ручьи' => 'ruçyi',
+               'ботсвана' => 'botsvana', 'большой' => 'bolşoy', 'большое' => 'bolşoye',
+               'большая' => 'bolşaya', 'ущелье' => 'uşçelye', 'ущельное' => 'uşçelnoye',
+               'предущельное' => 'preduşçelnoye', 'новенькое' => 'novenkoye', 'новосельц' => 'novoselts',
+               'мелко' => 'melko', 'овощ' => 'ovoşç', 'перепёлк' => 'perepölk', 'рощин' => 'roşçin',
+               'братск' => 'bratsk', 'краснофлотск' => 'krasnoflotsk', 'синицин' => 'sinitsin',
+               'синицын' => 'sinitsın', 'льгов' => 'lgov', 'желто' => 'jelto', 'жёлт' => 'jölt',
+               'пермь' => 'perm', 'солдатск' => 'soldatsk', 'кольцо' => 'koltso', 'шелко' => 'şelko',
+               'охотск' => 'ohotsk', 'марий эл' => 'mariy el', 'мариуполь' => 'mariupol',
+               'белгород' => 'belgorod', 'иркутск' => 'irkutsk', 'Иркутск' => 'İrkutsk', 'орёл' => 'oröl',
+               'рязанск' => 'râzansk', 'рязань' => 'râzan', 'тверск' => 'tversk', 'тверь' => 'tver',
+               'ярославль' => 'yaroslavl', 'благовеще' => 'blagoveşçe', 'мальдив' => 'maldiv',
+               'бальбек' => 'balbek', 'альчик' => 'alçik', 'харьков' => 'harkov', 'волынск' => 'volınsk',
+               'волынь' => 'volın',
 
-               # originally Latin to Cyrillic, deduped from above
-               'ань' => 'an', 'аньге' => 'ange', 'аньде' => 'ande', 'аньки' => 'anki', 'кёр' => 'kör',
-               'мэр' => 'mer', 'этсин' => 'etsin',
-
-               # exceptions added after speaker review
-               # see https://www.mediawiki.org/wiki/User:TJones_(WMF)/T23582
-               'аджизленювинъиз' => 'acizlenüviñiz', 'акъшам' => 'aqşam', 'алчакъгонъюлли' => 'alçaqgöñülli',
-               'аньанелер' => 'ananeler', 'аньанелеримиз' => 'ananelerimiz',
-               'аньанелеримизден' => 'ananelerimizden', 'аньанелеримизни' => 'ananelerimizni',
-               'аньанели' => 'ananeli', 'асфальтке' => 'asfaltke', 'баарьде' => 'baarde', 'бахтсыз' => 'bahtsız',
-               'берилюви' => 'berilüvi', 'берювден' => 'berüvden', 'берювни' => 'berüvni',
-               'большевиклер' => 'bolşevikler', 'большевиклерге' => 'bolşeviklerge', 'болюк' => 'bölük',
-               'болюнген' => 'bölüngen', 'болюнгенини' => 'bölüngenini', 'болюшип' => 'bölüşip',
-               'бугуннинъ' => 'bugünniñ', 'бугуньден' => 'bugünden', 'бугуньки' => 'bugünki',
-               'букюльген' => 'bükülgen', 'букюльди' => 'büküldi', 'буллюр' => 'büllür',
-               'бурюмчик' => 'bürümçik', 'бурюнген' => 'bürüngen', 'бутюн' => 'bütün', 'бутюнлей' => 'bütünley',
-               'буюген' => 'büyügen', 'буюй' => 'büyüy', 'волость' => 'volost', 'волостьларгъа' => 'volostlarğa',
-               'гонъюлини' => 'göñülini', 'гонъюлли' => 'göñülli', 'гонъюллилер' => 'göñülliler',
-               'госпиталинде' => 'gospitalinde', 'госпитальге' => 'gospitalge', 'госпитальде' => 'gospitalde',
-               'гренадёр' => 'grenadör', 'гугюм' => 'gügüm', 'гугюмлер' => 'gügümler',
-               'гугюмлери' => 'gügümleri', 'гугюмлерини' => 'gügümlerini', 'гурьсюльди' => 'gürsüldi',
-               'гурюльдештилер' => 'gürüldeştiler', 'гурюльти' => 'gürülti', 'гурюльтили' => 'gürültili',
-               'гурюльтисидир' => 'gürültisidir', 'дарульмуаллиминде' => 'darülmualliminde',
-               'дарульмуаллимининде' => 'darülmuallimininde', 'дарульмуаллиминнинъ' => 'darülmualliminniñ',
-               'дёгюльген' => 'dögülgen', 'декабрьде' => 'dekabrde', 'дёндюрилип' => 'döndürilip',
-               'дёнермиз' => 'dönermiz', 'дёнмектелер' => 'dönmekteler', 'денъишюв' => 'deñişüv',
-               'дёрдю' => 'dördü', 'дёрдюмиз' => 'dördümiz', 'дёрдюнджи' => 'dördünci', 'дёрт' => 'dört',
-               'дертлешювге' => 'dertleşüvge', 'джесюр' => 'cesür', 'джесюране' => 'cesürane',
-               'джесюрликлерини' => 'cesürliklerini', 'джонегенлерини' => 'cönegenlerini',
-               'джонедим' => 'cönedim', 'джонейлер' => 'cöneyler', 'джурьатсызлыгъына' => 'cüratsızlığına',
-               'дюгюнлер' => 'dügünler', 'дюгюнлерле' => 'dügünlerle', 'дюдюк' => 'düdük', 'дюльбер' => 'dülber',
-               'дюльбери' => 'dülberi', 'дюльберлер' => 'dülberler', 'дюльберлернинъ' => 'dülberlerniñ',
-               'дюльгер' => 'dülger', 'дюльгерге' => 'dülgerge', 'дюльгерлернинъки' => 'dülgerlerniñki',
-               'дюльгерни' => 'dülgerni', 'дюльгернинъ' => 'dülgerniñ', 'дюмбюрдетти' => 'dümbürdetti',
-               'дюмен' => 'dümen', 'дюмени' => 'dümeni', 'дюнья' => 'dünya', 'дюньявий' => 'dünyaviy',
-               'дюньяда' => 'dünyada', 'дюньяларгъа' => 'dünyalarğa', 'дюньяларда' => 'dünyalarda',
-               'дюньяны' => 'dünyanı', 'дюньянынъ' => 'dünyanıñ', 'дюньясы' => 'dünyası',
-               'ельаякълылар' => 'yelayaqlılar', 'елькъуваны' => 'yelquvanı', 'ильич' => 'i̇liç',
-               'ичюн' => 'içün', 'ичюнми' => 'içünmi', 'келюви' => 'kelüvi', 'келювини' => 'kelüvini',
-               'келювинъизде' => 'kelüviñizde', 'келювни' => 'kelüvni', 'кемирювлер' => 'kemirüvler',
-               'кесювде' => 'kesüvde', 'кетюв' => 'ketüv', 'кетювге' => 'ketüvge', 'кетюви' => 'ketüvi',
-               'кетювимни' => 'ketüvimni', 'кетювлер' => 'ketüvler', 'кетювлери' => 'ketüvleri',
-               'кетювлеринънинъ' => 'ketüvleriñniñ', 'кетювнинъ' => 'ketüvniñ', 'кирюв' => 'kirüv',
-               'князь' => 'knâz', 'козькъапакъларыны' => 'közqapaqlarını', 'козьлю' => 'közlü', 'козю' => 'közü',
-               'козюме' => 'közüme', 'козюнде' => 'közünde', 'козюне' => 'közüne', 'козюнен' => 'közünen',
-               'козюнинъ' => 'közüniñ', 'козюнъни' => 'közüñni', 'койлюде' => 'köylüde',
-               'койлюлер' => 'köylüler', 'койлюлерде' => 'köylülerde', 'койлюлерни' => 'köylülerni',
-               'койлюлернинъ' => 'köylülerniñ', 'койлюнинъ' => 'köylüniñ', 'коккозьге' => 'kökközge',
-               'коккозьде' => 'kökközde', 'коккозьдеки' => 'kökközdeki', 'коккозьден' => 'kökközden',
-               'кокюс' => 'köküs', 'кокюси' => 'köküsi', 'кокюсим' => 'köküsim', 'кокюсиме' => 'köküsime',
-               'кокюсинъе' => 'köküsiñe', 'комиссарлар' => 'komissarlar', 'комиссарлары' => 'komissarları',
-               'комитетининъ' => 'komitetiniñ', 'концлагерь' => 'kontslager', 'копьмеди' => 'köpmedi',
-               'копьти' => 'köpti', 'копюр' => 'köpür', 'копюрге' => 'köpürge', 'копюрден' => 'köpürden',
-               'копюри' => 'köpüri', 'копюрнинъ' => 'köpürniñ', 'коридорда' => 'koridorda',
-               'корьсюн' => 'körsün', 'корюв' => 'körüv', 'корюльген' => 'körülgen', 'корюнди' => 'köründi',
-               'корюндинъ' => 'köründiñ', 'корюне' => 'körüne', 'корюнип' => 'körünip',
-               'корюнмеген' => 'körünmegen', 'корюнмеди' => 'körünmedi', 'корюнмедилер' => 'körünmediler',
-               'корюнмей' => 'körünmey', 'корюнмейсинъиз' => 'körünmeysiñiz', 'корюнмекте' => 'körünmekte',
-               'корюнмектелер' => 'körünmekteler', 'корюнъиз' => 'körüñiz', 'корюше' => 'körüşe',
-               'корюшеджекмиз' => 'körüşecekmiz', 'корюшим' => 'körüşim', 'корюшип' => 'körüşip',
-               'корюширмиз' => 'körüşirmiz', 'корюшкен' => 'körüşken', 'корюшкенде' => 'körüşkende',
-               'корюшмеге' => 'körüşmege', 'корюшмегенимиз' => 'körüşmegenimiz', 'корюштик' => 'körüştik',
-               'корюштим' => 'körüştim', 'корюшюв' => 'körüşüv', 'корюшювде' => 'körüşüvde',
-               'корюшювден' => 'körüşüvden', 'корюшюви' => 'körüşüvi', 'корюшювимден' => 'körüşüvimden',
-               'корюшювимизге' => 'körüşüvimizge', 'корюшювимизден' => 'körüşüvimizden',
-               'костюми' => 'kostümi', 'кузю' => 'küzü', 'кулькюден' => 'külküden', 'кулькюнинъ' => 'külküniñ',
-               'кулькюсининъ' => 'külküsiniñ', 'кулю' => 'külü', 'кулюмсиреген' => 'külümsiregen',
-               'кулюмсиреди' => 'külümsiredi', 'кулюмсиредим' => 'külümsiredim', 'кулюмсирей' => 'külümsirey',
-               'кулюмсирейим' => 'külümsireyim', 'кулюмсиреп' => 'külümsirep', 'кулюни' => 'külüni',
-               'кулюнчли' => 'külünçli', 'кулюшинде' => 'külüşinde', 'кулюштилер' => 'külüştiler',
-               'кумюш' => 'kümüş', 'куньдюз' => 'kündüz', 'куньдюзлери' => 'kündüzleri', 'куньлюк' => 'künlük',
-               'куню' => 'künü', 'кунюмде' => 'künümde', 'кунюнде' => 'kününde', 'кунюндеми' => 'künündemi',
-               'кунюнъ' => 'künüñ', 'курькчю' => 'kürkçü', 'курьсю' => 'kürsü', 'курьсюге' => 'kürsüge',
-               'курьсюлер' => 'kürsüler', 'курючтен' => 'kürüçten', 'кутюклерни' => 'kütüklerni',
-               'кутюкли' => 'kütükli', 'кучьлю' => 'küçlü', 'кучьлюклер' => 'küçlükler',
-               'кучьсюнмезсинъ' => 'küçsünmezsiñ', 'кучюджик' => 'küçücik', 'кучюк' => 'küçük',
-               'кучюм' => 'küçüm', 'кучюмле' => 'küçümle', 'кучюнден' => 'küçünden', 'кучюни' => 'küçüni',
-               'къаарьлене' => 'qaarlene', 'къаарьли' => 'qaarli', 'къальбим' => 'qalbim',
-               'къальбимни' => 'qalbimni', 'къальбинде' => 'qalbinde', 'къальпли' => 'qalpli',
-               'къальптен' => 'qalpten', 'къалюбелядан' => 'qalübelâdan', 'къулюбенъде' => 'qulübeñde',
-               'лёман' => 'löman', 'львованынъ' => 'lvovanıñ', 'лютфи' => 'lütfi', 'лютфиге' => 'lütfige',
-               'лютфини' => 'lütfini', 'мазюн' => 'mazün', 'малюм' => 'malüm', 'малюмат' => 'malümat',
-               'махлюкъаттан' => 'mahlüqattan', 'махлюкътан' => 'mahlüqtan', 'махульдюрге' => 'mahuldürge',
-               'махульдюрде' => 'mahuldürde', 'махульдюрдеки' => 'mahuldürdeki',
-               'махульдюрден' => 'mahuldürden', 'махульдюрли' => 'mahuldürli',
-               'махульдюрлилер' => 'mahuldürliler', 'махульдюрлилермиз' => 'mahuldürlilermiz',
-               'махульдюрми' => 'mahuldürmi', 'махульдюрни' => 'mahuldürni', 'мевджут' => 'mevcut',
-               'мезкюр' => 'mezkür', 'мектюп' => 'mektüp', 'мектюпни' => 'mektüpni', 'мектюпте' => 'mektüpte',
-               'мелитопольге' => 'melitopolge', 'мемнюн' => 'memnün', 'мемнюниетле' => 'memnüniyetle',
-               'мемнюним' => 'memnünim', 'мемнюнмиз' => 'memnünmiz', 'менсюп' => 'mensüp',
-               'мешгъульмиз' => 'meşğulmiz', 'мулькюни' => 'mülküni', 'мумкюн' => 'mümkün',
-               'мумкюнми' => 'mümkünmi', 'мусульманлар' => 'musulmanlar', 'мусульманлармы' => 'musulmanlarmı',
-               'мухкемлендирюв' => 'mühkemlendirüv', 'мушкюль' => 'müşkül', 'ничюн' => 'niçün',
-               'ничюндир' => 'niçündir', 'нумюнеси' => 'nümünesi', 'огю' => 'ögü', 'огюз' => 'ögüz',
-               'огюмде' => 'ögümde', 'огюмдеки' => 'ögümdeki', 'огюме' => 'ögüme', 'огюмизге' => 'ögümizge',
-               'огюмизде' => 'ögümizde', 'огюмиздеки' => 'ögümizdeki', 'огюмни' => 'ögümni',
-               'огюнде' => 'ögünde', 'огюндеки' => 'ögündeki', 'огюндекиси' => 'ögündekisi',
-               'огюнден' => 'ögünden', 'огюне' => 'ögüne', 'огюнъизде' => 'ögüñizde', 'огютини' => 'ögütini',
-               'огютлерини' => 'ögütlerini', 'озю' => 'özü', 'озюм' => 'özüm', 'озюмден' => 'özümden',
-               'озюме' => 'özüme', 'озюмизни' => 'özümizni', 'озюмизнинъ' => 'özümizniñ',
-               'озюмизнинъки' => 'özümizniñki', 'озюмнен' => 'özümnen', 'озюмни' => 'özümni',
-               'озюмнинъ' => 'özümniñ', 'озюнде' => 'özünde', 'озюнден' => 'özünden', 'озюне' => 'özüne',
-               'озюнен' => 'özünen', 'озюни' => 'özüni', 'озюнинъ' => 'özüniñ', 'озюнинъкими' => 'özüniñkimi',
-               'озюнъ' => 'özüñ', 'озюнъе' => 'özüñe', 'озюнъиз' => 'özüñiz', 'озюнъиздеки' => 'özüñizdeki',
-               'озюнъни' => 'özüñni', 'оксюз' => 'öksüz', 'окюндим' => 'ökündim', 'ольдюрип' => 'öldürip',
-               'ольдюрмек' => 'öldürmek', 'ольдюрювде' => 'öldürüvde', 'ольчюде' => 'ölçüde', 'олюм' => 'ölüm',
-               'олюмден' => 'ölümden', 'олюмлер' => 'ölümler', 'омюр' => 'ömür', 'омюрге' => 'ömürge',
-               'омюри' => 'ömüri', 'опькеленюв' => 'öpkelenüv', 'орьтилюви' => 'örtilüvi', 'орьтюли' => 'örtüli',
-               'орюли' => 'örüli', 'орюлип' => 'örülip', 'осюв' => 'ösüv', 'осюмлик' => 'ösümlik',
-               'отькерювни' => 'ötkerüvni', 'отькюр' => 'ötkür', 'офицери' => 'ofitseri',
-               'офицерим' => 'ofitserim', 'офицерлер' => 'ofitserler', 'пальтосыны' => 'paltosını',
-               'пальтосынынъ' => 'paltosınıñ', 'пекинюв' => 'pekinüv', 'пекитювнинъ' => 'pekitüvniñ',
-               'пиширюв' => 'pişirüv', 'повидло' => 'povidlo', 'полис' => 'polis', 'полициясы' => 'politsiyası',
-               'помещик' => 'pomeşçik', 'потюк' => 'potük', 'потюклеринен' => 'potüklerinen',
-               'пулемёт' => 'pülemöt', 'пулемётларны' => 'pülemötlarnı', 'режиссёр' => 'rejissör',
-               'ролюнде' => 'rolünde', 'севастопольнинъ' => 'sevastopolniñ', 'сёгди' => 'sögdi', 'сёз' => 'söz',
-               'сёзлер' => 'sözler', 'сёзлери' => 'sözleri', 'сёзлерим' => 'sözlerim',
-               'сёзлеримден' => 'sözlerimden', 'сёзлериме' => 'sözlerime', 'сёзлеримни' => 'sözlerimni',
-               'сёзлеримнинъ' => 'sözlerimniñ', 'сёзлеринде' => 'sözlerinde', 'сёзлерине' => 'sözlerine',
-               'сёзлерини' => 'sözlerini', 'сёзлерининъ' => 'sözleriniñ', 'сёзлеринъиз' => 'sözleriñiz',
-               'сёзлеринъизни' => 'sözleriñizni', 'сёзлернен' => 'sözlernen', 'сёзлерни' => 'sözlerni',
-               'сёзлернинъ' => 'sözlerniñ', 'сёзнен' => 'söznen', 'сёзни' => 'sözni', 'сёзчиклер' => 'sözçikler',
-               'сёзчиклерден' => 'sözçiklerden', 'сёзю' => 'sözü', 'сёзюмен' => 'sözümen',
-               'сёзюмнинъ' => 'sözümniñ', 'сёзюне' => 'sözüne', 'сёзюни' => 'sözüni', 'сёзюнинъ' => 'sözüniñ',
-               'сёйле' => 'söyle', 'сёйлегенде' => 'söylegende', 'сёйлегенлеринден' => 'söylegenlerinden',
-               'сёйледи' => 'söyledi', 'сёйлей' => 'söyley', 'сёйленди' => 'söylendi',
-               'сёйленмеге' => 'söylenmege', 'сёйленмекте' => 'söylenmekte', 'сёйленъиз' => 'söyleñiz',
-               'сёнген' => 'söngen', 'сёнди' => 'söndi', 'сёндюрди' => 'söndürdi',
-               'сёндюрильген' => 'söndürilgen', 'сёндюрип' => 'söndürip', 'сентябрьнинъ' => 'sentâbrniñ',
-               'сергюзешт' => 'sergüzeşt', 'сергюзештлерни' => 'sergüzeştlerni',
-               'ставропольге' => 'stavropolge', 'сулькевич' => 'sulkeviç', 'сурьат' => 'surat',
-               'суфлёр' => 'suflör', 'сюеги' => 'süyegi', 'сюеклерге' => 'süyeklerge',
-               'сюйрекледи' => 'süyrekledi', 'сюйреле' => 'süyrele', 'сюйрен' => 'süyren',
-               'сюйренге' => 'süyrenge', 'сюйренде' => 'süyrende', 'сюйреп' => 'süyrep', 'сюйрю' => 'süyrü',
-               'сюкюнет' => 'sükünet', 'сюкюнети' => 'süküneti', 'сюкюнетте' => 'sükünette', 'сюкют' => 'süküt',
-               'сюляле' => 'sülâle', 'сюрген' => 'sürgen', 'сюрди' => 'sürdi', 'сюрмеди' => 'sürmedi',
-               'сюрюльмеген' => 'sürülmegen', 'сют' => 'süt', 'тебессюм' => 'tebessüm', 'тёкип' => 'tökip',
-               'тёкти' => 'tökti', 'тёкюльген' => 'tökülgen', 'тёкюльди' => 'töküldi',
-               'тёкюндиси' => 'tökündisi', 'тёле' => 'töle', 'тёледим' => 'töledim', 'телюке' => 'telüke',
-               'телюкели' => 'telükeli', 'тенеффюс' => 'teneffüs', 'тенеффюслер' => 'teneffüsler',
-               'тёпеге' => 'töpege', 'тёпелери' => 'töpeleri', 'тёпелерине' => 'töpelerine',
-               'тёпели' => 'töpeli', 'тёпеси' => 'töpesi', 'тёпесинден' => 'töpesinden',
-               'тёпесини' => 'töpesini', 'тёрге' => 'törge', 'тёрде' => 'törde', 'тёрдеки' => 'tördeki',
-               'тёрюне' => 'törüne', 'тешеббюсим' => 'teşebbüsim', 'тёшегинден' => 'töşeginden',
-               'тёшегине' => 'töşegine', 'тёшек' => 'töşek', 'тешеккюр' => 'teşekkür',
-               'тешеккюрлер' => 'teşekkürler', 'тёшекни' => 'töşekni', 'тёшектен' => 'töşekten',
-               'тёшели' => 'töşeli', 'тёшемек' => 'töşemek', 'тёшеп' => 'töşep', 'теэссюф' => 'teessüf',
-               'тюбю' => 'tübü', 'тюбюнде' => 'tübünde', 'тюбюндеки' => 'tübündeki', 'тюз' => 'tüz',
-               'тюзельгенге' => 'tüzelgenge', 'тюзельтмек' => 'tüzeltmek', 'тюземликлер' => 'tüzemlikler',
-               'тюзетип' => 'tüzetip', 'тюзетирим' => 'tüzetirim', 'тюзеткен' => 'tüzetken',
-               'тюзетмеге' => 'tüzetmege', 'тюзетмесенъ' => 'tüzetmeseñ', 'тюзетти' => 'tüzetti',
-               'тюзетюв' => 'tüzetüv', 'тюкенмез' => 'tükenmez', 'тюкюриктен' => 'tükürikten',
-               'тюкян' => 'tükân', 'тюкяны' => 'tükânı', 'тюкянында' => 'tükânında', 'тюм' => 'tüm',
-               'тюневин' => 'tünevin', 'тюневинки' => 'tünevinki', 'тюпсюз' => 'tüpsüz', 'тюрк' => 'türk',
-               'тюрклернинъ' => 'türklerniñ', 'тюркнинъ' => 'türkniñ', 'тюркче' => 'türkçe', 'тюркю' => 'türkü',
-               'тюркюлерини' => 'türkülerini', 'тюркюнинъ' => 'türküniñ', 'тюрлю' => 'türlü',
-               'тюртип' => 'türtip', 'тюрттинъиз' => 'türttiñiz', 'тютемекте' => 'tütemekte', 'тютюн' => 'tütün',
-               'тютюнджи' => 'tütünci', 'тюфеги' => 'tüfegi', 'тюфегини' => 'tüfegini', 'тюфек' => 'tüfek',
-               'тюфеклеринен' => 'tüfeklerinen', 'тюфеклернен' => 'tüfeklernen', 'тюфеклерни' => 'tüfeklerni',
-               'тюфекнен' => 'tüfeknen', 'тюфексиз' => 'tüfeksiz', 'тюш' => 'tüş', 'тюше' => 'tüşe',
-               'тюшеджек' => 'tüşecek', 'тюшеджексинъми' => 'tüşeceksiñmi', 'тюшем' => 'tüşem',
-               'тюшип' => 'tüşip', 'тюшкен' => 'tüşken', 'тюшкенде' => 'tüşkende', 'тюшкенлер' => 'tüşkenler',
-               'тюшмеге' => 'tüşmege', 'тюшмейим' => 'tüşmeyim', 'тюшмейлер' => 'tüşmeyler',
-               'тюшмек' => 'tüşmek', 'тюшмекте' => 'tüşmekte', 'тюшмеси' => 'tüşmesi', 'тюшсе' => 'tüşse',
-               'тюшти' => 'tüşti', 'тюштик' => 'tüştik', 'тюштилер' => 'tüştiler', 'тюштими' => 'tüştimi',
-               'тюштинъиз' => 'tüştiñiz', 'тюшювден' => 'tüşüvden', 'тюшюджек' => 'tüşücek',
-               'тюшюнген' => 'tüşüngen', 'тюшюнгендже' => 'tüşüngence', 'тюшюндже' => 'tüşünce',
-               'тюшюнджеге' => 'tüşüncege', 'тюшюнджелер' => 'tüşünceler', 'тюшюнджелери' => 'tüşünceleri',
-               'тюшюнджелерим' => 'tüşüncelerim', 'тюшюнджели' => 'tüşünceli', 'тюшюнджеси' => 'tüşüncesi',
-               'тюшюнди' => 'tüşündi', 'тюшюндим' => 'tüşündim', 'тюшюне' => 'tüşüne',
-               'тюшюнелер' => 'tüşüneler', 'тюшюнесинъиз' => 'tüşünesiñiz', 'тюшюнип' => 'tüşünip',
-               'тюшюнмеге' => 'tüşünmege', 'тюшюнмезсинъ' => 'tüşünmezsiñ', 'тюшюнмей' => 'tüşünmey',
-               'тюшюнмемек' => 'tüşünmemek', 'тюшюргенлер' => 'tüşürgenler', 'тюшюрди' => 'tüşürdi',
-               'тюшюрдик' => 'tüşürdik', 'тюшюре' => 'tüşüre', 'тюшюрип' => 'tüşürip', 'тюшюрмек' => 'tüşürmek',
-               'уджюм' => 'ücüm', 'удюр' => 'üdür', 'узюле' => 'üzüle', 'узюлип' => 'üzülip',
-               'узюльгенини' => 'üzülgenini', 'узюльди' => 'üzüldi', 'уйрюлип' => 'üyrülip',
-               'укюмет' => 'ükümet', 'укюмети' => 'ükümeti', 'укюметими' => 'ükümetimi',
-               'укюметимиз' => 'ükümetimiz', 'укюметини' => 'ükümetini', 'укюметининъ' => 'ükümetiniñ',
-               'укюметке' => 'ükümetke', 'укюметкеми' => 'ükümetkemi', 'укюметми' => 'ükümetmi',
-               'укюметнинъ' => 'ükümetniñ', 'укюметтен' => 'ükümetten', 'укюмран' => 'ükümran',
-               'улькюн' => 'ülkün', 'умюдим' => 'ümüdim', 'умют' => 'ümüt', 'умютлери' => 'ümütleri',
-               'умютсизден' => 'ümütsizden', 'усть' => 'üst', 'устьке' => 'üstke', 'устьлеринде' => 'üstlerinde',
-               'устьлериндеки' => 'üstlerindeki', 'устьлерине' => 'üstlerine', 'устьлерини' => 'üstlerini',
-               'устюрткъа' => 'üsturtqa', 'усьнюхаткъа' => 'üsnühatqa', 'усьнюхаты' => 'üsnühatı',
-               'усьтю' => 'üstü', 'усьтюмде' => 'üstümde', 'усьтюмдеки' => 'üstümdeki', 'усьтюме' => 'üstüme',
-               'усьтюнде' => 'üstünde', 'усьтюндеки' => 'üstündeki', 'усьтюндемиз' => 'üstündemiz',
-               'усьтюне' => 'üstüne', 'усьтюни' => 'üstüni', 'усьтюнлик' => 'üstünlik',
-               'усьтюнъизге' => 'üstüñizge', 'утёкунь' => 'ütökün', 'уфюрди' => 'üfürdi', 'учю' => 'üçü',
-               'учюмиз' => 'üçümiz', 'учюн' => 'üçün', 'учюнджи' => 'üçünci', 'учюнджисининъ' => 'üçüncisiniñ',
-               'ушюй' => 'üşüy', 'ушюмез' => 'üşümez', 'ушюмезсинъ' => 'üşümezsiñ',
-               'факультетинде' => 'fakultetinde', 'факультетине' => 'fakultetine',
-               'февральнинъ' => 'fevralniñ', 'харьковдаки' => 'harkovdaki', 'харьковдан' => 'harkovdan',
-               'чёкти' => 'çökti', 'чёкюрли' => 'çökürli', 'чёкюч' => 'çöküç', 'чёллюкке' => 'çöllükke',
-               'чёль' => 'çöl', 'чёльде' => 'çölde', 'чёльмек' => 'çölmek', 'чёткю' => 'çötkü',
-               'чёчамийлер' => 'çöçamiyler', 'чюнки' => 'çünki', 'чюрюди' => 'çürüdi', 'чюрюк' => 'çürük',
-               'шукюр' => 'şükür', 'шукюрлер' => 'şükürler', 'этюв' => 'etüv', 'этювден' => 'etüvden',
-               'этюви' => 'etüvi', 'этюдлар' => 'etüdlar', 'юзден' => 'yüzden', 'юзлеп' => 'yüzlep',
-               'юзлерини' => 'yüzlerini', 'юзлернен' => 'yüzlernen', 'юзлюги' => 'yüzlügi',
-               'юзлюкке' => 'yüzlükke', 'юзю' => 'yüzü', 'юзюм' => 'yüzüm', 'юзюме' => 'yüzüme',
-               'юзюмен' => 'yüzümen', 'юзюмни' => 'yüzümni', 'юзюнде' => 'yüzünde', 'юзюни' => 'yüzüni',
-               'юзюнинъ' => 'yüzüniñ', 'юзюнъ' => 'yüzüñ', 'юзюнъизге' => 'yüzüñizge', 'юклю' => 'yüklü',
-               'юксельтюв' => 'yükseltüv', 'юньлю' => 'yünlü', 'юньлюдже' => 'yünlüce',
-               'юртсеверлик' => 'yurtseverlik', 'юртюде' => 'yürtüde', 'юрьтю' => 'yürtü',
-               'юрьтюге' => 'yürtüge', 'юрьтюнинъ' => 'yürtüniñ', 'юрюльсе' => 'yürülse', 'юрюнъиз' => 'yürüñiz',
-               'юрюш' => 'yürüş', 'юрюши' => 'yürüşi', 'юрюшим' => 'yürüşim', 'юрюшини' => 'yürüşini',
-               'юрюшнен' => 'yürüşnen', 'юрюшни' => 'yürüşni',
        ];
 
-       # map Cyrillic to Latin and back, whole word match only
+       # map Cyrillic to Latin and back, simple string match only (no regex)
        # no variants: map exactly as is
-       # items with capture group refs (e.g., $1) are only mapped from the
-       # regex to the reference
        private $exactCaseMappings = [
                # аббревиатуры
                # abbreviations
-               'ОБСЕ' => 'OBSE', 'КъМДж' => 'QMC', 'КъАЭ' => 'QAE', 'ГъСМК' => 'ĞSMK', 'ШСДжБ' => 'ŞSCB',
-               'КъМШСДж' => 'QMŞSC', 'КъДМПУ' => 'QDMPU', 'КъМПУ' => 'QMPU', 'КъЮШ' => 'QYŞ', 'ЮШ' => 'YŞ',
+               'ОБСЕ' => 'OBSE', 'КъМДж' => 'QMC', 'КъДж' => 'QC', 'КъАЭ' => 'QAE', 'ГъСМК' => 'ĞSMK',
+               'ШСДжБ' => 'ŞSCB', 'КъМШСДж' => 'QMŞSC', 'КъАССР' => 'QASSR', 'КъДМПУ' => 'QDMPU',
+               'КъМПУ' => 'QMPU',
        ];
 
        # map Cyrillic to Latin and back, match end of word
@@ -517,10 +356,12 @@ class CrhExceptions {
                # originally C2L
                'иаль' => 'ial', 'нуль' => 'nul', 'кой' => 'köy', 'койнинъ' => 'köyniñ', 'койни' => 'köyni',
                'койге' => 'köyge', 'койде' => 'köyde', 'койдеки' => 'köydeki', 'койден' => 'köyden',
-               'козь' => 'köz',
+               'козь' => 'köz', '-юнджи' => '-ünci', '-юнджиде' => '-üncide', '-юнджиден' => '-ünciden',
 
                # originally L2C, here swapped
-               'етсин' => 'etsin',
+               'етсин' => 'etsin', 'льная' => 'lnaya', 'льное' => 'lnoye', 'льный' => 'lnıy', 'льний' => 'lniy',
+               'льская' => 'lskaya', 'льский' => 'lskiy', 'льское' => 'lskoye', 'ополь' => 'opol',
+               'щее' => 'şçeye', 'щий' => 'şçiy', 'щая' => 'şçaya', 'цепс' => 'tseps',
 
        ];
 
@@ -533,15 +374,18 @@ class CrhExceptions {
                'буюк([^ъ])' => 'büyük$1', 'бую([гдйлмнпрстчшc])(и)' => 'büyü$1$2',
                'буют([^ыа])' => 'büyüt$1', 'джонк([^ъ])' => 'cönk$1', 'коюм' => 'köyüm', 'коюнъ' => 'köyüñ',
                'коюн([ди])' => 'köyün$1', 'куе' => 'küye', 'куркке' => 'kürkke', 'куркни' => 'kürkni',
-               'куркте' => 'kürkte', 'куркчи' => 'kürkçi', 'куркчю' => 'kürkçü',
+               'куркте' => 'kürkte', 'куркчю' => 'kürkçü', 'кою' => 'köyü',
+               'жизнь' => 'jizn',
 
                # арабизмы на муи- муэ- / Arabic муи- муэ-
                'му([иэИЭ])' => 'mü$1',
 
                # originally L2C, here swapped
-               'итъаль' => 'ital',
                'роль$1' => 'rol([^ü])',
-               'усть$1' => 'üst([knt])',
+               'усть$1' => 'üst([^ü])',
+
+               # more prefixes
+               'ком-кок' => 'köm-kök',
 
        ];
 
@@ -555,8 +399,68 @@ class CrhExceptions {
                        # относятся ко всему слову #
                        # whole words              #
                        ############################
-                       '/\b([34])(\-)юнджи\b/u' => '$1$2ünci',
-                       '/\b([34])(\-)ЮНДЖИ\b/u' => '$1$2ÜNCİ',
+
+                       // TODO: refactor upper/lower/first capital whole words without
+                       // regexes into simpler list
+
+                       '/\bКъЮШ\b/u' => 'QYŞ',
+                       '/\bЮШ\b/u' => 'YŞ',
+
+                       '/\bкок\b/u' => 'kök',
+                       '/\bКок\b/u' => 'Kök',
+                       '/\bКОК\b/u' => 'KÖK',
+                       '/\bком-кок\b/u' => 'köm-kök',
+                       '/\bКом-кок\b/u' => 'Köm-kök',
+                       '/\bКОМ-КОК\b/u' => 'KÖM-KÖK',
+
+                       '/\bкоп\b/u' => 'köp',
+                       '/\bКоп\b/u' => 'Köp',
+                       '/\bКОП\b/u' => 'KÖP',
+
+                       '/\bкурк\b/u' => 'kürk',
+                       '/\bКурк\b/u' => 'Kürk',
+                       '/\bКУРК\b/u' => 'KÜRK',
+
+                       '/\bог\b/u' => 'ög',
+                       '/\bОг\b/u' => 'Ög',
+                       '/\bОГ\b/u' => 'ÖG',
+
+                       '/\bюрип\b/u' => 'yürip',
+                       '/\bЮрип\b/u' => 'Yürip',
+                       '/\bЮРИП\b/u' => 'YÜRİP',
+
+                       '/\bюз\b/u' => 'yüz',
+                       '/\bЮз\b/u' => 'Yüz',
+                       '/\bЮЗ\b/u' => 'YÜZ',
+
+                       '/\bюк\b/u' => 'yük',
+                       '/\bЮк\b/u' => 'Yük',
+                       '/\bЮК\b/u' => 'YÜK',
+
+                       '/\bбуюп\b/u' => 'büyüp',
+                       '/\bБуюп\b/u' => 'Büyüp',
+                       '/\bБУЮП\b/u' => 'BÜYÜP',
+
+                       '/\bбуюк\b/u' => 'büyük',
+                       '/\bБуюк\b/u' => 'Büyük',
+                       '/\bБУЮК\b/u' => 'BÜYÜK',
+
+                       '/\bджонк\b/u' => 'cönk',
+                       '/\bДжонк\b/u' => 'Cönk',
+                       '/\bДЖОНК\b/u' => 'CÖNK',
+                       '/\bджонкю\b/u' => 'cönkü',
+                       '/\bДжонкю\b/u' => 'Cönkü',
+                       '/\bДЖОНКЮ\b/u' => 'CÖNKÜ',
+
+                       '/\bустке\b/u' => 'üstke',
+                       '/\bУстке\b/u' => 'Üstke',
+                       '/\bУСТКЕ\b/u' => 'ÜSTKE',
+                       '/\bустте\b/u' => 'üstte',
+                       '/\bУстте\b/u' => 'Üstte',
+                       '/\bУСТТЕ\b/u' => 'ÜSTTE',
+                       '/\bусттен\b/u' => 'üstten',
+                       '/\bУсттен\b/u' => 'Üstten',
+                       '/\bУСТТЕН\b/u' => 'ÜSTTEN',
 
                        # отдельно стоящие Ё и Я
                        # stand-alone Ё and Я
@@ -570,6 +474,16 @@ class CrhExceptions {
                        '/\bКъЮШн/u' => 'QYŞn',
                        '/\bЮШн/u' => 'YŞn',
 
+                       # need to convert digraphs (гъ, къ, нъ, дж) now to match patterns
+                       '/гъ/u' => 'ğ',
+                       '/Г[ъЪ]/u' => 'Ğ',
+                       '/къ/u' => 'q',
+                       '/К[ъЪ]/u' => 'Q',
+                       '/нъ/u' => 'ñ',
+                       '/Н[ъЪ]/u' => 'Ñ',
+                       '/дж/u' => 'c',
+                       '/Д[жЖ]/u' => 'C',
+
                        # о => ö
                        '/\b(['.Crh::C_M_CONS.'])о(['.Crh::C_CONS.'])(['.Crh::C_CONS.'])([еиэюьü])/u' => '$1ö$2$3$4',
                        '/\bо(['.Crh::C_CONS.'])(['.Crh::C_CONS.'])([еиэюьü])/u' => 'ö$1$2$3',
@@ -662,63 +576,101 @@ class CrhExceptions {
                ];
 
                $this->Latn2CyrlRegexes = [
+
+                       // TODO: refactor upper/lower/first capital whole words without
+                       // regexes into simpler list
+
+                       '/\ban\b/u' => 'ань',
+                       '/\bAn\b/u' => 'Ань',
+                       '/\bAN\b/u' => 'АНЬ',
+                       '/\bange\b/u' => 'аньге',
+                       '/\bAnge\b/u' => 'Аньге',
+                       '/\bANGE\b/u' => 'АНЬГЕ',
+                       '/\bande\b/u' => 'аньде',
+                       '/\bAnde\b/u' => 'Аньде',
+                       '/\bANDE\b/u' => 'АНЬДЕ',
+                       '/\banki\b/u' => 'аньки',
+                       '/\bAnki\b/u' => 'Аньки',
+                       '/\bANKİ\b/u' => 'АНЬКИ',
+                       '/\bderal\b/u' => 'деръал',
+                       '/\bDeral\b/u' => 'Деръал',
+                       '/\bDERAL\b/u' => 'ДЕРЪАЛ',
+                       '/\bkör\b/u' => 'кёр',
+                       '/\bKör\b/u' => 'Кёр',
+                       '/\bKÖR\b/u' => 'КЁР',
+                       '/\bmer\b/u' => 'мэр',
+                       '/\bMer\b/u' => 'Мэр',
+                       '/\bMER\b/u' => 'МЭР',
+
+                       '/\bджонк/u' => 'cönk',
+                       '/\bДжонк/u' => 'Cönk',
+                       '/\bДЖОНК/u' => 'CÖNK',
+
+                       '/\bкуркчи/u' => 'kürkçi',
+                       '/\bКуркчи/u' => 'Kürkçi',
+                       '/\bКУРКЧИ/u' => 'KÜRKÇI',
+
                        # буква Ё - первый заход
                        # расставляем Ь после согласных
-                       '/^([yY])ö(['.Crh::L_N_CONS.'])([aAuU'.Crh::L_CONS.']|$)/u' => '$1ö$2ь$3',
-                       '/^([yY])Ö(['.Crh::L_N_CONS.'])([aAuU'.Crh::L_CONS.']|$)/u' => '$1Ö$2Ь$3',
-                       '/^AQŞ(['.Crh::WORD_ENDS.'ngd])/u' => 'АКъШ$1',
+                       '/\b([yY])ö(['.Crh::L_N_CONS.'])([aAuU'.Crh::L_CONS.']|\b)/u' => '$1ö$2ь$3',
+                       '/\b([yY])Ö(['.Crh::L_N_CONS.'])([aAuU'.Crh::L_CONS.']|\b)/u' => '$1Ö$2Ь$3',
+                       '/\bAQŞ([^AEI]|\b)/u' => 'АКъШ$1',
 
                        # буква Ю - первый заход
                        # расставляем Ь после согласных
-                       '/^([yY])ü(['.Crh::L_N_CONS.'])([aAuU'.Crh::L_CONS.']|$)/u' => '$1ü$2ь$3',
-                       '/^([yY])Ü(['.Crh::L_N_CONS.'])([aAuU'.Crh::L_CONS.']|$)/u' => '$1Ü$2Ь$3',
+                       '/\b([yY])ü(['.Crh::L_N_CONS.'])([aAuU'.Crh::L_CONS.']|\b)/u' => '$1ü$2ь$3',
+                       '/\b([yY])Ü(['.Crh::L_N_CONS.'])([aAuU'.Crh::L_CONS.']|\b)/u' => '$1Ü$2Ь$3',
 
-                       '/^([bcgkpşBCGKPŞ])ö(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|$)/u' => '$1ö$2ь$3',
-                       '/^([bcgkpşBCGKPŞ])Ö(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|$)/u' => '$1Ö$2Ь$3',
-                       '/^([bcgkpşBCGKPŞ])Ö(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|$)/u' => '$1Ö$2Ь$3',
-                       '/^([bcgkpşBCGKPŞ])ü(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|$)/u' => '$1ü$2ь$3',
-                       '/^([bcgkpşBCGKPŞ])Ü(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|$)/u' => '$1Ü$2Ь$3',
-                       '/^([bcgkpşBCGKPŞ])Ü(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|$)/u' => '$1Ü$2Ь$3',
+                       '/\b([bcgkpşBCGKPŞ])ö(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|\b)/u' => '$1ö$2ь$3',
+                       '/\b([bcgkpşBCGKPŞ])Ö(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|\b)/u' => '$1Ö$2Ь$3',
+                       '/\b([bcgkpşBCGKPŞ])Ö(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|\b)/u' => '$1Ö$2Ь$3',
+                       '/\b([bcgkpşBCGKPŞ])ü(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|\b)/u' => '$1ü$2ь$3',
+                       '/\b([bcgkpşBCGKPŞ])Ü(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|\b)/u' => '$1Ü$2Ь$3',
+                       '/\b([bcgkpşBCGKPŞ])Ü(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|\b)/u' => '$1Ü$2Ь$3',
 
                         # ö и ü в начале слова
                         # случаи, когда нужен Ь
-                       '/^ö(['.Crh::L_N_CONS.'pP])(['.Crh::L_CONS.']|$)/u' => 'ö$1ь$2',
-                       '/^Ö(['.Crh::L_N_CONS_LC.'p])(['.Crh::L_CONS.']|$)/u' => 'Ö$1ь$2',
-                       '/^Ö(['.Crh::L_N_CONS_UC.'P])(['.Crh::L_CONS.']|$)/u' => 'Ö$1Ь$2',
-                       '/^ü(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|$)/u' => 'ü$1ь$2',
-                       '/^Ü(['.Crh::L_N_CONS_LC.'])(['.Crh::L_CONS.']|$)/u' => 'Ü$1ь$2',
-                       '/^Ü(['.Crh::L_N_CONS_UC.'])(['.Crh::L_CONS.']|$)/u' => 'Ü$1Ь$2',
-
-                       '/ts$/u' => 'ц',
-                       '/şç$/u' => 'щ',
-                       '/Ş[çÇ]$/u' => 'Щ',
-                       '/T[sS]$/u' => 'Ц',
+                       '/\bö(['.Crh::L_N_CONS.'pP])(['.Crh::L_CONS.']|\b)/u' => 'ö$1ь$2',
+                       '/\bÖ(['.Crh::L_N_CONS_LC.'p])(['.Crh::L_CONS.']|\b)/u' => 'Ö$1ь$2',
+                       '/\bÖ(['.Crh::L_N_CONS_UC.'P])(['.Crh::L_CONS.']|\b)/u' => 'Ö$1Ь$2',
+                       '/\bü(['.Crh::L_N_CONS.'])(['.Crh::L_CONS.']|\b)/u' => 'ü$1ь$2',
+                       '/\bÜ(['.Crh::L_N_CONS_LC.'])(['.Crh::L_CONS.']|\b)/u' => 'Ü$1ь$2',
+                       '/\bÜ(['.Crh::L_N_CONS_UC.'])(['.Crh::L_CONS.']|\b)/u' => 'Ü$1Ь$2',
+
+                       '/ts\b/u' => 'ц',
+                       '/şç\b/u' => 'щ',
+                       '/Ş[çÇ]\b/u' => 'Щ',
+                       '/T[sS]\b/u' => 'Ц',
 
                        # Ь после Л
                        # add Ь after Л
-                       '/(['.Crh::L_F.'])l(['.Crh::L_CONS_LC.']|$)/u' => '$1ль$2',
-                       '/(['.Crh::L_F_UC.'])L(['.Crh::L_CONS.']|$)/u' => '$1ЛЬ$2',
+                       '/(['.Crh::L_F.'])l(['.Crh::L_CONS_LC.']|\b)/u' => '$1ль$2',
+                       '/(['.Crh::L_F_UC.'])L(['.Crh::L_CONS.']|\b)/u' => '$1ЛЬ$2',
+
+                       '/etsin\b/u' => 'етсин',
+                       '/Etsin\b/u' => 'Етсин',
+                       '/ETSİN\b/u' => 'ЕТСИН',
 
                        # относятся к началу слова
-                       '/^ts/u' => 'ц',
-                       '/^T[sS]/u' => 'Ц',
+                       '/\bts/u' => 'ц',
+                       '/\bT[sS]/u' => 'Ц',
 
-                       '/^şç/u' => 'щ',
-                       '/^Ş[çÇ]/u' => 'Щ',
+                       '/\bşç/u' => 'щ',
+                       '/\bŞ[çÇ]/u' => 'Щ',
 
                        # Э
-                       '/(^|['.Crh::L_VOW.'аеэяАЕЭЯ])e/u' => '$1э',
-                       '/(^|['.Crh::L_VOW_UC.'АЕЭЯ])E/u' => '$1Э',
+                       '/(\b|['.Crh::L_VOW.'аеэяАЕЭЯ])e/u' => '$1э',
+                       '/(\b|['.Crh::L_VOW_UC.'АЕЭЯ])E/u' => '$1Э',
 
-                       '/^(['.Crh::L_M_CONS.'])ö/u' => '$1о',
-                       '/^(['.Crh::L_M_CONS.'])Ö/u' => '$1О',
-                       '/^(['.Crh::L_M_CONS.'])ü/u' => '$1у',
-                       '/^(['.Crh::L_M_CONS.'])Ü/u' => '$1У',
+                       '/\b(['.Crh::L_M_CONS.'])ö/u' => '$1о',
+                       '/\b(['.Crh::L_M_CONS.'])Ö/u' => '$1О',
+                       '/\b(['.Crh::L_M_CONS.'])ü/u' => '$1у',
+                       '/\b(['.Crh::L_M_CONS.'])Ü/u' => '$1У',
 
-                       '/^ö/u' => 'о',
-                       '/^Ö/u' => 'О',
-                       '/^ü/u' => 'у',
-                       '/^Ü/u' => 'У',
+                       '/\bö/u' => 'о',
+                       '/\bÖ/u' => 'О',
+                       '/\bü/u' => 'у',
+                       '/\bÜ/u' => 'У',
 
                        # некоторые исключения
                        # some exceptions
@@ -780,13 +732,18 @@ class CrhExceptions {
                        '/[ьЬ]([aA])/u' => '$1',
 
                        # дж
-                       '/C(['.Crh::L_UC.Crh::C_UC.'Ъ])/u' => 'ДЖ$1',
+                       '/C(['.Crh::L_UC.Crh::C_UC.'АЕЁЙОУЭЮЯ])/u' => 'ДЖ$1',
+                       '/(['.Crh::L_UC.Crh::C_UC.'АЕЁЙОУЭЮЯ])C/u' => '$1ДЖ',
 
                        # гъ, къ, нъ
-                       # гъ, къ, нъ
-                       '/Ğ(['.Crh::L_UC.Crh::C_UC.'Ъ])/u' => 'ГЪ$1',
-                       '/Q(['.Crh::L_UC.Crh::C_UC.'Ъ])/u' => 'КЪ$1',
-                       '/Ñ(['.Crh::L_UC.Crh::C_UC.'Ъ])/u' => 'НЪ$1',
+                       '/Ğ(['.Crh::L_UC.Crh::C_UC.'])/u' => 'ГЪ$1',
+                       '/(['.Crh::L_UC.Crh::C_UC.'Ъ])Ğ/u' => '$1ГЪ',
+
+                       '/Q(['.Crh::L_UC.Crh::C_UC.'])/u' => 'КЪ$1',
+                       '/(['.Crh::L_UC.Crh::C_UC.'Ъ])Q/u' => '$1КЪ',
+
+                       '/Ñ(['.Crh::L_UC.Crh::C_UC.'])/u' => 'НЪ$1',
+                       '/(['.Crh::L_UC.Crh::C_UC.'Ъ])Ñ/u' => '$1НЪ',
 
                ];
        }
index 710d6be..9943212 100644 (file)
@@ -48,6 +48,7 @@ class Names {
        public static $names = [
                'aa' => 'Qafár af', # Afar
                'ab' => 'Аҧсшәа', # Abkhaz
+               'abs' => 'bahasa ambon', # Ambonese Malay, T193566
                'ace' => 'Acèh', # Aceh
                'ady' => 'адыгабзэ', # Adyghe
                'ady-cyrl' => 'адыгабзэ', # Adyghe
@@ -148,7 +149,7 @@ class Names {
                'en-gb' => 'British English', # British English
                'eo' => 'Esperanto', # Esperanto
                'es' => 'español', # Spanish
-               'es-formal' => 'español (formal)', # Spanish formal address
+               'es-formal' => "español (formal)\xE2\x80\x8E", # Spanish formal address
                'et' => 'eesti', # Estonian
                'eu' => 'euskara', # Basque
                'ext' => 'estremeñu', # Extremaduran
@@ -198,7 +199,7 @@ class Names {
                'hsb' => 'hornjoserbsce', # Upper Sorbian
                'ht' => 'Kreyòl ayisyen', # Haitian Creole French
                'hu' => 'magyar', # Hungarian
-               'hu-formal' => 'magyar (formal)', # Hungarian formal address
+               'hu-formal' => "magyar (formal)\xE2\x80\x8E", # Hungarian formal address
                'hy' => 'Հայերեն', # Armenian
                'hz' => 'Otsiherero', # Herero
                'ia' => 'interlingua', # Interlingua (IALA)
@@ -242,7 +243,7 @@ class Names {
                'km' => 'ភាសាខ្មែរ', # Khmer, Central
                'kn' => 'ಕನ್ನಡ', # Kannada
                'ko' => '한국어', # Korean
-               'ko-kp' => '한국어 (조선)', # Korean (DPRK)
+               'ko-kp' => '조선말', # Korean (DPRK), T190324
                'koi' => 'Перем Коми', # Komi-Permyak
                'kr' => 'Kanuri', # Kanuri, Central
                'krc' => 'къарачай-малкъар', # Karachay-Balkar
diff --git a/languages/i18n/abs.json b/languages/i18n/abs.json
new file mode 100644 (file)
index 0000000..f3ff0f8
--- /dev/null
@@ -0,0 +1,529 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Anok kutai jang",
+                       "Bonaditya"
+               ]
+       },
+       "tog-oldsig": "Ale pung tanda tangan yang su ada:",
+       "tog-fancysig": "Biking ale pung tanda tangan tu akang jadi teks wiki (seng parlu pranala otomatis lai)",
+       "tog-uselivepreview": "Kas lia pralia deng seng kas muat kintal ulang",
+       "tog-forceeditsummary": "Kas inga beta kal parobahan pung kontak ringkasan mase kosong",
+       "tog-watchlisthideown": "Jang kas lia beta pung parobahan dalang daftar pantou",
+       "tog-watchlisthidebots": "Jang kas lia bot pung parobahan dia da biking dalang daftar pantou",
+       "tog-watchlisthideminor": "Jang kas lia parobahan yang kacil-kacil dalang daftar pantou",
+       "tog-watchlisthideliu": "Jang kas lia pangguna yang su maso log pung parobahan dalang daftar pantou",
+       "tog-watchlisthideanons": "Jang kas lia pangguna seng jalas pung parobahan dalang daftar pantou",
+       "tog-watchlisthidepatrolled": "Jas kas lia parobahan yang su dapa patroli dalang daftar pantou",
+       "tog-watchlisthidecategorization": "Jang kas lia kintal pung kategorisasi",
+       "tog-ccmeonemails": "TOlong kiring beta salinan yang beta da kiring par orang laeng",
+       "tog-diffonly": "Jang kas lia dalang kintal di bawah parobahan yang beda-beda jua",
+       "tog-showhiddencats": "Kas lia kategori tasambunyi",
+       "sunday": "Dominggu/Minggu",
+       "monday": "Senin",
+       "tuesday": "Salasa",
+       "wednesday": "Rabu",
+       "thursday": "Kamis",
+       "friday": "Jumat",
+       "saturday": "Sabtu",
+       "sun": "Min",
+       "mon": "Sen",
+       "tue": "Sal",
+       "wed": "Rab",
+       "thu": "Kam",
+       "fri": "Jum",
+       "sat": "Sab",
+       "january": "Januari",
+       "february": "Pebuari",
+       "march": "Maret",
+       "april": "April",
+       "may_long": "Mey",
+       "june": "Juni",
+       "july": "Juli",
+       "august": "Agustus",
+       "september": "September",
+       "october": "Oktober",
+       "november": "Nopember",
+       "december": "Desember",
+       "january-gen": "Januari",
+       "february-gen": "Februari",
+       "march-gen": "Maret",
+       "april-gen": "April",
+       "may-gen": "Mei",
+       "june-gen": "Juni",
+       "july-gen": "Juli",
+       "august-gen": "Agustus",
+       "september-gen": "September",
+       "october-gen": "Oktober",
+       "november-gen": "November",
+       "december-gen": "Desember",
+       "jan": "Jan",
+       "feb": "Feb",
+       "mar": "Mar",
+       "apr": "Apr",
+       "may": "Mei",
+       "jun": "Jun",
+       "jul": "Jul",
+       "aug": "Ags",
+       "sep": "Sep",
+       "oct": "Okt",
+       "nov": "Nov",
+       "dec": "Des",
+       "pagecategories": "{{PLURAL:$1lKategori}}",
+       "category_header": "Kintal dalang kategori \"$1\"",
+       "subcategories": "Subkategori",
+       "category-media-header": "Media dalang kategori \"$1\"",
+       "hidden-categories": "{{PLURAL:$1|Kategori tasambunyi}}",
+       "category-subcat-count": "{{PLURAL:$2|Akang kategori ada punya {{PLURAL:$1|$1 subkategori}} berikut, dar total $2.}}",
+       "category-article-count": "{{PLURAL:$2|Akang kategori ada punya {{PLURAL:$1|$1 kintal}}, dar total $2.}}",
+       "category-file-count": "{{PLURAL:$2|Kategori ini punya {{PLURAL:$1|$1 berkas}} berikut, dar total $2.}}",
+       "broken-file-category": "Kintal deng akang pung gambar ancor",
+       "about": "Tentang",
+       "newwindow": "(buka di jandela baru)",
+       "cancel": "Kas batal",
+       "mytalk": "Basumbang suara",
+       "navigation": "Navigasi",
+       "and": "&#32;deng",
+       "namespaces": "Tampa nama",
+       "variants": "Jenis-jenis",
+       "navigation-heading": "Menu kas tentu arah",
+       "returnto": "Kombale ka $1.",
+       "tagline": "Dar {{SITENAME}}",
+       "help": "Pertolongan",
+       "search": "Dapa cari",
+       "searchbutton": "Cari",
+       "searcharticle": "Lanjut",
+       "history": "Sejarah kintal",
+       "history_short": "Versi lama",
+       "printableversion": "Jenis taceta",
+       "permalink": "Pranala permanen",
+       "view": "Lia",
+       "view-foreign": "Lia di $1",
+       "edit": "Perbaiki Akang",
+       "create": "Biking",
+       "create-local": "Kas tamba deskripsi lokal",
+       "newpage": "Kintal baru",
+       "talkpagelinktext": "bicara",
+       "specialpage": "Kintal spesial",
+       "personaltools": "Alat-alat sandiri",
+       "talk": "Pambicaraan",
+       "views": "Tampilan",
+       "toolbox": "Alat",
+       "tool-link-userrights": "Simpang kalopmpok {{GENDER:$1|pangguna}}",
+       "tool-link-userrights-readonly": "Lia kalompok {{GENDER:$1|pangguna}}",
+       "tool-link-emailuser": "Kiring pasang ka akang {{GENDER:$1|pangguna}}",
+       "imagepage": "Lia berkas pung kintal",
+       "mediawikipage": "Lia sistem pung kintal pasang",
+       "templatepage": "Lia kintal tamplet",
+       "viewhelppage": "Lia bantuan pung kintal",
+       "categorypage": "Lia kategori pung kintal",
+       "viewtalkpage": "Lia kintal par basumbang suara",
+       "otherlanguages": "Dalang bahasa laeng",
+       "redirectedfrom": "(Dapa pindah dar $1)",
+       "redirectpagesub": "kintal voor pengalihan",
+       "redirectto": "Pi ka:",
+       "lastmodifiedat": "Kintal akang dong ubah terakhir tanggal $1, jam $2.",
+       "viewcount": "Akang kintal su diakses sabanya {{PLURAL:$1|$1 kali}}.<br />",
+       "protectedpage": "Kintal yang dapa lindung",
+       "jumpto": "Balumpa ka:",
+       "jumptonavigation": "penentu arah",
+       "jumptosearch": "cari",
+       "view-pool-error": "Aoe, mohon maaf jua e, server dong sakaran ada sibuk. Talalu banya pangguna yang coba mo balia akang kintal. Tunggu sadiki do sabalong Ale coba akses akang kintal sakali lai.\n\n$1",
+       "generic-pool-error": "Aoe, mohon maaf jua e, server dong sakaran ada sibuk. Talalu banya pangguna yang coba mo balia akang sumber. Tunggu sadiki do sabalong Ale coba akses akang kintal sakali lai.\n\n$1",
+       "pool-timeout": "Waktu su talewat par batunggu konci",
+       "aboutsite": "Tentang {{SITENAME}}",
+       "aboutpage": "Project:Tentang",
+       "copyrightpage": "{{ns:project}}:Hak cipta",
+       "currentevents": "Kajadiang sakarang",
+       "currentevents-url": "Project:Kajadiang sakarang",
+       "disclaimers": "Panyangkalan",
+       "disclaimerpage": "Project:Panyangkalan umum",
+       "edithelp": "Bantuan mo robah",
+       "mainpage": "Kintal Muka",
+       "mainpage-description": "Kintal muka",
+       "portal": "Portal kalompok",
+       "portal-url": "Project:Portal kalompok",
+       "privacy": "Urusan sandiri",
+       "privacypage": "Project:Kebijakan privasi",
+       "retrievedfrom": "Dapa dar \"$1\"",
+       "newmessageslinkplural": "{{PLURAL:$1|pasang baru|pasang baru}}",
+       "newmessagesdifflinkplural": "{{PLURAL:$1|parobahan|999=parobahan}} terakhir",
+       "editsection": "kas ubah",
+       "editold": "kas ubah",
+       "editlink": "kas ubah",
+       "viewsourcelink": "lia sumber data",
+       "editsectionhint": "Kas ubah bagian: $1",
+       "toc": "Isi dalang",
+       "site-atom-feed": "Atom pung umpan $1",
+       "page-atom-feed": "Umpan Atom \"$1\"",
+       "red-link-title": "$1 (halaman seng ada)",
+       "nstab-main": "Kintal",
+       "nstab-user": "Pangguna",
+       "nstab-special": "Kintal spesial",
+       "nstab-project": "Proyek pung kintal",
+       "nstab-image": "Berkas",
+       "nstab-mediawiki": "Pasang",
+       "nstab-template": "Templat",
+       "nstab-category": "Kategori",
+       "mainpage-nstab": "Kintal muka",
+       "nosuchspecialpage": "Seng ada kintal yang spesial bagitu",
+       "nospecialpagetext": "<strong>Ale mo kintal istimewa, akang seng sah.</strong>\n\nDaftar kintal istimewa, akang sah, ale dong bole lia di [[Special:SpecialPages|{{int:specialpages}}]].",
+       "badtitle": "Judul seng bae",
+       "badtitletext": "Kintal pung judul yang ale dong minta seng sah, seng ada, ka judul lintas bahasa deng lintas wiki salah sambung",
+       "viewsource": "lia sumber data",
+       "viewsource-title": "Lia sumber par $1",
+       "viewsourcetext": "Ale dapa lia ka salin sumber dar akang kintal",
+       "userlogin-yourname": "Nama pangguna",
+       "userlogin-yourname-ph": "Kas maso ale pung nama pangguna",
+       "userlogin-yourpassword": "Kata pengaman",
+       "userlogin-yourpassword-ph": "Kas mato ale pung kata pengaman",
+       "createacct-yourpassword-ph": "Kas maso kata pengaman",
+       "createacct-yourpasswordagain": "Kas konfirm ale pung kata pengaman",
+       "createacct-yourpasswordagain-ph": "Kas maso sakali lai ale pung kata pengaman",
+       "userlogin-remembermypassword": "Biarkan beta tetap maso jua",
+       "login": "Maso dalang Log",
+       "userlogin-noaccount": "Seng ada akun?",
+       "userlogin-joinproject": "Baiko {{SITENAME}}",
+       "createaccount": "Biking akun",
+       "userlogin-resetpassword-link": "Seng inga ale pung kata sandi ka?",
+       "userlogin-helplink2": "Bantuan par maso log",
+       "createacct-emailoptional": "Email pung alamat (kal bisa, isi)",
+       "createacct-email-ph": "Kas maso ale pung email",
+       "createacct-submit": "Biking ale pung akun",
+       "createacct-benefit-heading": "{{SITENAME}} dibiking dar orang-orang macang ale dong.",
+       "createacct-benefit-body1": "{{PLURAL:$1|parobahan}}",
+       "createacct-benefit-body2": "{{PLURAL:$1|halaman}}",
+       "createacct-benefit-body3": "{{PLURAL:$1|kontributor}} terakhir",
+       "loginlanguagelabel": "Bahasa: $1",
+       "pt-login": "Maso dalang Log",
+       "pt-login-button": "Maso dalang Log",
+       "pt-createaccount": "Biking Akun",
+       "pt-userlogout": "Kaluar log",
+       "passwordreset": "Robah kata pengaman",
+       "bold_sample": "Teks akang bakal dong cetak tabal",
+       "bold_tip": "Teks tabal",
+       "italic_sample": "Teks akang dong cetak miring",
+       "italic_tip": "Teks miring",
+       "link_sample": "Pranala pung judul",
+       "link_tip": "Pranal internal",
+       "extlink_sample": "http://www.example.com pranala pung judul",
+       "extlink_tip": "Pranala luar (jang lupa awalan http:// )",
+       "headline_sample": "Teks judul",
+       "nowiki_sample": "Kas maso teks yang seng mau dapa format ka sini",
+       "nowiki_tip": "Jang talalu pikir wiki pung format",
+       "image_tip": "Kas tambah berkas",
+       "media_tip": "Pranala ka berkas",
+       "sig_tip": "Ale pung tanda tangan deng tanda waktu",
+       "hr_tip": "Garis horizontal",
+       "summary": "Ringkasan",
+       "minoredit": "Akang dapa robah sadiki sa",
+       "watchthis": "Pantou akang kintal",
+       "savearticle": "Simpang kintal",
+       "preview": "Pralia",
+       "showpreview": "Lia tayang lama",
+       "showdiff": "Lia parobahan",
+       "loginreqlink": "maso dalang log",
+       "noarticletext": "Sakarang seng ada tulisan di akang kintal.\nAle dapa [[Special:Search/{{PAGENAME}}| cari ini kintal pung judul]] di kintal akang, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cari log taika], kal seng [{{fullurl:{{FULLPAGENAME}}|action=edit}} biking kintal akang]</span>.",
+       "userpage-userdoesnotexist-view": "Pangguna \"$1\" akang seng tadaftar.",
+       "editing": "Marobah $1",
+       "creating": "Biking $!",
+       "editingsection": "Ada marobah $1 (bagian)",
+       "templatesused": "{{PLURAL:$1|Templat|Templat}} yang dipake dalang akang kintal:",
+       "template-protected": "(dapa lindung)",
+       "template-semiprotected": "(dilindungi sapanggal)",
+       "hiddencategories": "Akang kintal, anggota dar {{PLURAL:$1|1 kategori tasambunyi|$1 kategori tasambunyi}}:",
+       "permissionserrorstext-withaction": "Ale seng punya hak par akses $2, tagal {{PLURAL:$1|alasan|alasan}} akang:",
+       "moveddeleted-notice": "Akang kintal su dihapus.\nLog voor hapus, perlindungan, deng kas pindah dar kintal yang ada di bawah par referensi.",
+       "content-model-wikitext": "teks wiki",
+       "viewpagelogs": "Lia akang kintal pung log",
+       "currentrev-asof": "Revisi terakhir di $1",
+       "revisionasof": "Parobahan par $1",
+       "revision-info": "Revisi $1 dar {{GENDER:$6|$2}}$7",
+       "previousrevision": "← Parobahan lama",
+       "nextrevision": "Revisi kamuka →",
+       "currentrevisionlink": "Revisi terakhir",
+       "cur": "skr",
+       "last": "sabalong",
+       "history-fieldset-title": "Cari revisi",
+       "histfirst": "paleng lama",
+       "histlast": "paleng baru",
+       "history-feed-title": "Sejarah revisi",
+       "history-feed-description": "Akang kintal pung sejarah revisi dalang wiki",
+       "history-feed-item-nocomment": "$1 dalang $2",
+       "rev-delundel": "robah akang jadi talia ka seng",
+       "history-title": "Sejarah parobahan dar \"$1\"",
+       "difference-title": "$1: Parobahan pung beda",
+       "lineno": "Baris $1",
+       "compareselectedversions": "Kas banding versi yang su dipilih",
+       "editundo": "kas bale",
+       "diff-multi-sameuser": "({{PLURAL:$1|$1 revisi antara}} dar pangguna yang seng dapa kase lia)",
+       "searchresults": "Hasil bacari",
+       "searchresults-title": "Hasil par cari $1",
+       "prevn": "{{PLURAL:$1|$1}} sabalongnya",
+       "nextn": "{{PLURAL:$1|$1}} kamuka",
+       "nextn-title": "$1 {{PLURAL:$1|hasil|hasil}} kamuka",
+       "shown-title": "Kas lia $1 {{PLURAL:$1|hasil}} tiap kintal",
+       "viewprevnext": "Lia ($1 {{int:pipe-separator}} $2) ($3)",
+       "searchmenu-new": "<strong>Biking kintal \"[[:$1]]\" di akang wiki!</strong> {{PLURAL:$2|0=|Lia lai kintal yang su didapa dar ale su pancari.|Lia lai hasil pancarian yang su dijumpa.}}",
+       "searchprofile-articles": "Kintal dalang",
+       "searchprofile-images": "Multimedia",
+       "searchprofile-everything": "Samua",
+       "searchprofile-advanced": "Lanjutan",
+       "searchprofile-articles-tooltip": "Cari di $1",
+       "searchprofile-images-tooltip": "Pancari berkas",
+       "searchprofile-everything-tooltip": "Cari samua hal (termaso kintal par baku suara)",
+       "searchprofile-advanced-tooltip": "Cari di tampa nama spesial",
+       "search-result-size": "$1 ({{PLURAL:$2|1 kata|$2 kata}})",
+       "search-result-category-size": "{{PLURAL:$1|1 anggota|$1 anggota}} ({{PLURAL:$2|1 subkategori|$2 subkategori}}, {{PLURAL:$3|1 berkas|$3 berkas}})",
+       "search-redirect": "(Dapa pindah dar $1)",
+       "search-section": "(bagian $1)",
+       "search-suggest": "Mangkali ale maksud bagini: $1",
+       "searchall": "samua",
+       "search-showingresults": "{{PLURAL:$4|Hasil <strong>$1</strong> dar <strong>$3</strong>|Hasil <strong>$1 - $2</strong> dar <strong>$3</strong>}}",
+       "search-nonefound": "Seng ada hasil yang cocok deng kriteria",
+       "mypreferences": "Preferensi",
+       "right-writeapi": "Bapake API penulisan",
+       "newuserlogpage": "Log pambiking pangguna baru",
+       "action-edit": "kas bae kintal",
+       "enhancedrc-history": "versi lama",
+       "recentchanges": "Perubahan baru",
+       "recentchanges-legend": "Pilihan parobahan baru-baru",
+       "recentchanges-summary": "Cari parobahan baru dalang wiki di dalang akang kintal.",
+       "recentchanges-noresult": "Seng ada parobahan dalang waktu akang yang cocok deng kriteria.",
+       "recentchanges-feed-description": "Cari parobahan baru dalang wiki di dalang akang umpan",
+       "recentchanges-label-newpage": "Robah sabiji ini la biking kintal baru",
+       "recentchanges-label-minor": "Akang dapa robah sadiki sa",
+       "recentchanges-label-bot": "Bot yang robah akang",
+       "recentchanges-label-unpatrolled": "Parobahan yang balong dipantou",
+       "recentchanges-label-plusminus": "Parobahan ukuran akang kintal dalang bita",
+       "recentchanges-legend-heading": "<strong>Pambiking jelas:</strong>",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (lia lai [[Special:NewPages|daftar kintal baru]])",
+       "rcnotefrom": "Di bawah {{PLURAL:$5|parobahan}} dar <strong>$3, $4</strong> (dapa kas lia sampe <strong>$1</strong> parobahan).",
+       "rclistfrom": "Kas lia parobahan baru dar $2, $3",
+       "rcshowhideminor": "$1 robahan kacil",
+       "rcshowhideminor-show": "Kas tunjuk kamuka",
+       "rcshowhideminor-hide": "Jang kas lia",
+       "rcshowhidebots": "$1 bot",
+       "rcshowhidebots-show": "Kas tunjuk kamuka",
+       "rcshowhidebots-hide": "Jang kas lia",
+       "rcshowhideliu": "$1 pengguna tadaftar",
+       "rcshowhideliu-show": "Kas tunjuk kamuka",
+       "rcshowhideliu-hide": "Jang kas lia",
+       "rcshowhideanons": "$1 pengguna babayang",
+       "rcshowhideanons-show": "Kas tunjuk kamuka",
+       "rcshowhideanons-hide": "Jang kas lia",
+       "rcshowhidemine": "$1 beta pung daftar parobahan",
+       "rcshowhidemine-show": "Kas tunjuk kamuka",
+       "rcshowhidemine-hide": "Jang kas lia",
+       "rclinks": "Kas lia $1 parobahan baru dalang $2 hari terakhir",
+       "diff": "beda",
+       "hist": "versi",
+       "hide": "Jang kas lia",
+       "show": "Kas tunjuk kamuka",
+       "rc-change-size-new": "$1 {{PLURAL:$1|bita}} kal su barobah",
+       "recentchangeslinked": "Parobahan taika",
+       "recentchangeslinked-toolbox": "Parobahan taika",
+       "recentchangeslinked-title": "Parobahan taika deng \"$1\"",
+       "recentchangeslinked-page": "Kintal pung nama",
+       "recentchangeslinked-to": "Kas lia parobahan dar kintal banya yang tahubung deng kintal yang dong ada sajikan",
+       "upload": "Kas maso berkas",
+       "uploadlogpage": "Log par catat aktivias kas maso di internet",
+       "filedesc": "Ringkasan",
+       "license-header": "Jenis lisensi",
+       "imgfile": "berkas",
+       "file-anchor-link": "Berkas",
+       "filehist": "Berkas pung sejarah",
+       "filehist-help": "Klik tanggal/waktu par lia akang di tanggal/waktu yang diklik",
+       "filehist-current": "sakarang",
+       "filehist-datetime": "Tanggal/Waktu",
+       "filehist-thumb": "Miniatur",
+       "filehist-thumbtext": "Miniatur par versi par $1",
+       "filehist-user": "Pangguna",
+       "filehist-dimensions": "Dimensi",
+       "filehist-comment": "Komentar",
+       "linkstoimage": "{{PLURAL:$1|Kintal berikut}} punya pranala ka akang berkas:",
+       "nolinkstoimage": "Seng ada kintal yang punya sambungan ka akang berkas.",
+       "sharedupload-desc-here": "Akang berkas dar $1 deng mangkali dipake dalang proyek laeng.\nDeskripsi dar [$2 deksripsi pung kintal] dapa kas tunjuk di bawah.",
+       "upload-disallowed-here": "Ale seng bole timpa akang berkas",
+       "randompage": "Halaman sabarang",
+       "nbytes": "$1 {{PLURAL:$1|bita}}",
+       "nmembers": "$1 {{PLURAL:$1|anggota}}",
+       "prefixindex": "Samua kintal deng awalan",
+       "newpages": "Kintal baru",
+       "move": "Kas pindah",
+       "pager-newer-n": "{{PLURAL:$1|1 labe baru|$1 labe baru}}",
+       "pager-older-n": "{{PLURAL:$1|$1 labe lama}}",
+       "booksources": "Sumber buku",
+       "booksources-search-legend": "Cari par sumber buku",
+       "booksources-search": "Cari",
+       "speciallogtitlelabel": "Target (judul ka {{ns:pangguna}}:nama pangguna par pangguna)",
+       "log": "Catatan",
+       "allarticles": "Samua kintal",
+       "allpagessubmit": "Pi",
+       "categories": "Kategori-kategori",
+       "usermessage-editor": "Panyampe pasang sistem",
+       "watchlist": "Daftar pantou",
+       "mywatchlist": "Daftar pantou",
+       "watch": "Pantou",
+       "unwatch": "Seng ka batal pantou",
+       "rollbacklink": "kas kombale",
+       "rollbacklinkcount": "kas kombale $1 {{PLURAL:$1|parobahan}}",
+       "protectedarticle": "kas lindung \"[[$1]]\"",
+       "protect-default": "Kas izin samua pangguna",
+       "restriction-edit": "Kas ubah",
+       "restriction-move": "Kas pindah",
+       "namespace": "Tampa nama",
+       "invert": "Kas kombale yang ale su pilih",
+       "tooltip-invert": "Kas tanda par seng kas lia kintal pung parobahan dalang tampa nama yang su dipilih (ka tampa nama laeng yang taika kal dapa kas tanda)",
+       "namespace_association": "Tampa nama taika",
+       "tooltip-namespace_association": "Kas tanda kintal par taro tampa nama par basuara ka subjek taika deng tampa nama tapilih",
+       "blanknamespace": "(Muka)",
+       "contributions": "Kontribusi {{GENDER:$1|pangguna}}",
+       "contributions-title": "Kontribusi pangguna par $1",
+       "mycontris": "Kontribusi",
+       "anoncontribs": "Kontribusi",
+       "contribsub2": "Voor {{GENDER:$3|$1}} ($2)",
+       "uctop": "(sakarang)",
+       "month": "Dar bulan (deng sabalong)",
+       "year": "Dar taong (deng sabalong):",
+       "sp-contributions-newbies": "Kas lia yang dar pangguna baru sa",
+       "sp-contributions-blocklog": "catatan blokir",
+       "sp-contributions-uploads": "Kas maso ka internet",
+       "sp-contributions-logs": "log",
+       "sp-contributions-talk": "basumbang suara",
+       "sp-contributions-search": "Cari kontribusi",
+       "sp-contributions-username": "IP pung alamat ka nama pangguna:",
+       "sp-contributions-toponly": "Kas lia versi paleng atas sa",
+       "sp-contributions-newonly": "Kas lia cuma yang biking kintal baru sa",
+       "sp-contributions-submit": "Cari",
+       "whatlinkshere-title": "Kintal yang ada pranala ka \"$1\"",
+       "whatlinkshere-page": "Kintal",
+       "nolinkshere": "Seng ada halaman yang taika par <strong>[[:$1]]</strong>.",
+       "isredirect": "kintal voor pengalihan",
+       "istemplate": "tranklusi",
+       "whatlinkshere-prev": "{{PLURAL:$1|sabalong $1}}",
+       "whatlinkshere-next": "{{PLURAL:$1|kamuka $1}}",
+       "whatlinkshere-links": "← pranala",
+       "whatlinkshere-hideredirs": "$1 pangalihan",
+       "whatlinkshere-hidetrans": "$1 transklusi",
+       "whatlinkshere-hidelinks": "$1 pranala",
+       "whatlinkshere-filters": "Alat saring",
+       "ipboptions": "2 jam:2 hours,1 hari:1 day,3 hari:3 days,1 minggu:1 week,2 minggu:2 weeks,1 bulan:1 month,3 bulan:3 months,6 bulan:6 months,1 taong:1 year,salamanya:infinite",
+       "blocklink": "blokir",
+       "contribslink": "gabung",
+       "blocklogpage": "Catatan pamblokiran",
+       "blocklogentry": "blokir [[$1]] sampe $2 $3",
+       "export": "Kas ekspor kintal",
+       "thumbnail-more": "Kas basar",
+       "tooltip-pt-userpage": "Kintal {{GENDER:|Ale pung pangguna}}",
+       "tooltip-pt-mytalk": "Kintal par dong bicara di {{GENDER:|ale pung tampa basumbang suara}}",
+       "tooltip-pt-preferences": "Preferensi {{GENDER:|Ale}}",
+       "tooltip-pt-watchlist": "Daftar kintal yang beta pantou",
+       "tooltip-pt-mycontris": "Daftar kontribusi {{GENDER:|Ale}}",
+       "tooltip-pt-login": "Ale sabae nya maso log, la biar akang seng musti diwajibkan",
+       "tooltip-pt-logout": "Kaluar log",
+       "tooltip-pt-createaccount": "Ale dorang dapa saran par biking akun deng maso log, biar kata akang seng wajib",
+       "tooltip-ca-talk": "Pambincaraan halaman isi",
+       "tooltip-ca-edit": "Kas bae kintal",
+       "tooltip-ca-addsection": "Mulai bagian baru",
+       "tooltip-ca-viewsource": "Kintal akang dapa lindu. Ale cuma dapa lia akang pung sumber",
+       "tooltip-ca-history": "Lia versi terakhir sabalong dapa ubah",
+       "tooltip-ca-move": "Kas pindah akang kintal",
+       "tooltip-ca-watch": "Kas maso akang kintal par ale pung daftar lia-jaga",
+       "tooltip-ca-unwatch": "Hapus akang kintal dar ale pung daftar pantou",
+       "tooltip-search": "Sila cari jua dalang wiki ini mo",
+       "tooltip-search-go": "Pi ka kintal deng nama yang parsis bagitu kal tersedia",
+       "tooltip-search-fulltext": "Sila cari halaman yang ada tulisan macang bagini",
+       "tooltip-p-logo": "Ronda ka Halaman Muka",
+       "tooltip-n-mainpage": "Ronda ka Kintal Muka",
+       "tooltip-n-mainpage-description": "Ronda ka Halaman Muka",
+       "tooltip-n-portal": "Tentang projek, apa yang bole ale perbuat, apa yang bole ale lakukang",
+       "tooltip-n-currentevents": "Baku dapa deng informasi tentang kajadiang baru",
+       "tooltip-n-recentchanges": "Daftar prubahan yang baru dalang wiki",
+       "tooltip-n-randompage": "Kas kaluar akang sabarang halaman",
+       "tooltip-n-help": "Tampa par dapa tolong",
+       "tooltip-t-whatlinkshere": "Daftar samua wiki yang ada punya pranala par akang halaman",
+       "tooltip-t-recentchangeslinked": "Parobahan akhir-akhir dar kintal yang punya pranala par akang kintal",
+       "tooltip-feed-atom": "Umpan Atom par kintal akang",
+       "tooltip-t-contributions": "Daftar kontribusi dar {{GENDER:$1|pangguna akang}}",
+       "tooltip-t-upload": "Kas maso berkas",
+       "tooltip-t-specialpages": "Muatan samua halaman istimewa e",
+       "tooltip-t-print": "Kintal akang dalang versi tacetak",
+       "tooltip-t-permalink": "Pranala permanen par akang kintal pung revisi",
+       "tooltip-ca-nstab-main": "Lia kintal pung isi",
+       "tooltip-ca-nstab-user": "Lia pangguna pung kintal",
+       "tooltip-ca-nstab-special": "Akang ni kintal spesial, seng dapa ubah",
+       "tooltip-ca-nstab-project": "Lia proyek pung kintal",
+       "tooltip-ca-nstab-image": "Lia berkas pung kintal",
+       "tooltip-ca-nstab-mediawiki": "Lia pasang pung sistem",
+       "tooltip-ca-nstab-template": "Lia templat",
+       "tooltip-ca-nstab-category": "Lia kategori pung kintal",
+       "tooltip-minoredit": "Kas tanda akang, akang ni robah sadiki sa",
+       "tooltip-save": "Simpang ale pung parobahan",
+       "tooltip-preview": "Lia sakali lai ale pung parobahan. Pake ini jual sabalom manyimpang.",
+       "tooltip-diff": "Kas lia parobahan yang ale su ada biking",
+       "tooltip-compareselectedversions": "Lia beda dar dua versi kintal yang dipilih",
+       "tooltip-watch": "Kas maso akang kintal dalang ale pung daftar pantou",
+       "tooltip-rollback": "\"Kas bale\" kas batal samua dong ada robah ka versi paleng baru dalang satu klik sa",
+       "tooltip-summary": "Kas maso ringkasan pendek",
+       "simpleantispam-label": "Par periksa anti-manyampah.\n<strong>Jangan</strong> isi akang!",
+       "pageinfo-title": "Informasi par \"$1\"",
+       "pageinfo-header-basic": "Informasi dasar",
+       "pageinfo-header-edits": "Sejarah parobahan",
+       "pageinfo-header-restrictions": "Kintal pung perlindungan",
+       "pageinfo-display-title": "Kapala judul par ditampilkan",
+       "pageinfo-default-sort": "Konci taurut taika",
+       "pageinfo-length": "Kintal pung panjang (dalang bita)",
+       "pageinfo-article-id": "Kintal pung ID",
+       "pageinfo-language": "Dalang kintal pung bahasa",
+       "pageinfo-content-model": "Dalang kintal pung model",
+       "pageinfo-robot-policy": "Indeks samua disusun oleh robot",
+       "pageinfo-watchers": "Samua yang pantou kintal",
+       "pageinfo-redirects-name": "Samua pengalihan ka akang kintal",
+       "pageinfo-firstuser": "Kintal pung pambiking",
+       "pageinfo-firsttime": "Biking kintal pung tanggal",
+       "pageinfo-lastuser": "Parobah terakhir",
+       "pageinfo-lasttime": "Parobahan terakhir pung tanggal",
+       "pageinfo-edits": "Total samua parobahan",
+       "pageinfo-authors": "Samua penulis berbeda pung jumlah",
+       "pageinfo-recent-edits": "Jumlah yang da robah (dalang $1 terakhir)",
+       "pageinfo-recent-authors": "Samua penulis sakarang pung jumlah",
+       "pageinfo-toolboxlink": "Kintal pung info",
+       "previousdiff": "← Revisi sabalongnya",
+       "nextdiff": "Revisi kamuka →",
+       "file-info-size": "$1 × $2 piksel, berkas pung ukuran: $3, tipe MIME: $4",
+       "file-nohires": "Seng ada resoulusi yang labe tinggi",
+       "svg-long-desc": "Berkas SVG, nominal $1 × $2 piksel, basar berkas: $3",
+       "show-big-image": "Ukuran batol-batol",
+       "show-big-image-preview": "Ukuran akang ini: $1.",
+       "show-big-image-other": "{{PLURAL:$2|Resolusi}} laeng: $1.",
+       "show-big-image-size": "$1 x $2 piksel",
+       "bad_image_list": "Formatnya sebagai berikut:\n\nCuma sabutir daftar (baris yang awal e ada tanda *) yang di itong. Pranala yang pertama di suatu baris musti ke benda yang busu. Pranala isalanjutnya pada baris akang jua dianggap sbagai pengecualian, yaitu halaman yang dapa kas kluar akang.",
+       "metadata": "Metadata",
+       "metadata-fields": "Metadata gambar pung bidang dalam akang pasang, bakal katong kas maso dalang kintal gambar pung tampilan kal metadata pung tabel su dikase kacil.\nData laeng dong seng kas lia secara bawaan.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
+       "exif-orientation": "Arah",
+       "exif-xresolution": "Resolusi horizontal",
+       "exif-yresolution": "Resolusi vertikal",
+       "exif-datetime": "Tanggal deng waktu parobahan berkas",
+       "exif-make": "Pambiking kamera",
+       "exif-model": "Jenis kamera",
+       "exif-software": "Parangka lombo",
+       "exif-exifversion": "Versi Exif",
+       "exif-colorspace": "Tampa warna",
+       "exif-datetimeoriginal": "Tanggal deng waktu par biking data",
+       "exif-datetimedigitized": "Tanggal deng waktu voor kas digital",
+       "exif-orientation-1": "Normal",
+       "namespacesall": "samua",
+       "monthsall": "samua",
+       "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|basuara]])",
+       "specialpages": "Kintal spesial",
+       "tag-filter": "Alat saring [[Special:Tags|tag]]:",
+       "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag}}]]: $2)",
+       "logentry-delete-delete": "$1 {{GENDER:$2|manghapus}} kintal $3",
+       "logentry-move-move": "$1 {{GENDER:$2|kas pindah}} kintal $3 ka $4",
+       "logentry-move-move_redir": "$1 {{GENDER:$2|kas pindah}} kintal $3 ke $4 kas tutup yang lama",
+       "logentry-newusers-create": "$1 {{GENDER:$2|biking}} akun pangguna",
+       "logentry-upload-upload": "$1 {{GENDER:$2|kas maso di internet}} $3",
+       "searchsuggest-search": "Cari {{SITENAME}}",
+       "duration-days": "$1 {{PLURAL:$1|hari}}"
+}
index ed36e93..b08ae3a 100644 (file)
        "permissionserrorstext": "Droëneuh hana geupeuidin keu neupubuët nyoë, muroë {{PLURAL:$1|dalèh}} nyoë:",
        "permissionserrorstext-withaction": "Droëneuh hana hak tamöng keu $2, muroë {{PLURAL:$1|choë|choë}} nyoë:",
        "recreate-moveddeleted-warn": "'''Ingat: Droëneuh neupeugöt ulang saboh laman nyang ka tom geusampôh. ''',\n\nNeutimang-timang dilèë peuë ék patôt neupeulanjut atra nyang teungöh neu’andam.\nNyoë pat nakeuh log seunampôh nibak laman nyoë:",
-       "moveddeleted-notice": "Laman nyoë ka geusampôh.\nLog seunampôh ngön log pinah laman nyoë geupeuseudia di yup nyoë keu keuneubah.",
+       "moveddeleted-notice": "Mieng nyoë ka geusampôh.\nLog seunampôh, neulindông ngön peuninah keu mieng nyoe na di yup nyoe keu catatan.",
        "log-fulllog": "Eu ban dum ceunatat",
        "edit-hook-aborted": "Seunampôh geupeubateuë lé kaw'ét parser.\nHana jeuneulaih.",
        "edit-gone-missing": "Han jeut peubarô ôn.\nÔn nyoe mungkén ka geusampôh.",
        "mergelog": "Peugabông log",
        "revertmerge": "Hana jadèh peugabông",
        "history-title": "Riwayat geunantoë nibak \"$1\"",
+       "difference-title": "Bida antara revisi nibak \"$1\"",
        "lineno": "Baréh $1:",
        "compareselectedversions": "Peubandéng curak teupiléh",
        "editundo": "pubateuë",
        "diff-empty": "(Hana bida)",
+       "diff-multi-sameuser": "({{PLURAL:$1|Saboh revisi antara|$1 revisi antara}} lé ureueng ngui nyang saban hana geupeudeuih)",
        "searchresults": "Hasé mita",
        "searchresults-title": "Hasé mita keu \"$1\"",
        "notextmatches": "Hana naseukah laman nyang pah",
        "recentchangeslinked-feed": "Neuubah meuhubông",
        "recentchangeslinked-toolbox": "Neuubah teukaw'èt",
        "recentchangeslinked-title": "Neuubah nyang meukaw'èt ngön $1",
-       "recentchangeslinked-summary": "Nyoë nakeuh dapeuta neuubah nyang geupeugèt ban-ban nyoë keu on-on nyang meuhubông nibak ôn ka kusuih (atawa keu anggèëta kawan kusuih).\nÔn-ôn bak [[Special:Watchlist|keunalon droeneuh]] geucitak '''teubay'''.",
+       "recentchangeslinked-summary": "Neupasoe saboh nan mieng keu neueu neuubah bak mieng nyang mupawôt u atawa nibak mieng nyan. (Keu neueu anggèeta nibak saboh kawan, neupasoe Kawan:Nan kawan). Neuubah bak mieng bak [[Special:Watchlist|dapeuta keunalön]] teutuléh <strong>teubai</strong>.",
        "recentchangeslinked-page": "Nan miëng:",
        "recentchangeslinked-to": "Peuleumah neuubah nibak laman-laman nyang mupawôt ngön laman nyang geubri",
        "upload": "Peutamöng beureukaih",
        "pager-older-n": "{{PLURAL:$1|1 leubèh awai|$1 leubèh awai}}",
        "booksources": "Nè kitab",
        "booksources-search-legend": "Mita bak nè kitab",
+       "booksources-search": "Mita",
        "specialloguserlabel": "Ureuëng ngui:",
        "speciallogtitlelabel": "Sasaran (judu atawa {{ns:ureueng ngui}}:nan ureueng ngui keu ureueng ngui)",
        "log": "Log",
        "tag-filter-submit": "Saréng",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag}}]]: $2)",
        "logentry-delete-delete": "$1 {{GENDER:$2|geusampôh}} miëng $3",
+       "logentry-move-move": "$1 {{GENDER:$2|geupinah}} mieng $3 u $4",
        "logentry-newusers-create": "$1 {{GENDER:$2|geupeugöt}} akun ureuëng ngui",
        "logentry-upload-upload": "$1 {{GENDER:$2|geupasoe}} $3",
        "searchsuggest-search": "Mita {{SITENAME}}",
index c4eb969..89fd60b 100644 (file)
        "unlinkaccounts": "إزالة ربط الحسابات",
        "unlinkaccounts-success": "الحساب تم فك وصله.",
        "authenticationdatachange-ignored": "تغيير بيانات التحقق لم يتم التعامل معه. ربما لم يتم ضبط موفر؟",
-       "userjsispublic": "من فضلك لاحظ: صفحات الجافاسكريبت الفرعية لا ينبغي أن تحتوي غلى بيانات سرية بما أنها يمكن رؤيتها بواسطة المستخدمين الآخرين.",
+       "userjsispublic": "'''من فضلك لاحظ:''' صفحات الجافاسكريبت الفرعية لا ينبغي أن تحتوي على بياناتٍ سرية، وذلك لأنه يمكن مشاهدتها بواسطة المستخدمين الآخرين.",
        "userjsonispublic": "الرجاء ملاحظة أنه: يجب ألا تحتوي الصفحات الفرعية لجسون على بيانات سرية لأنها قابلة للعرض من قبل المستخدمين الآخرين.",
        "usercssispublic": "من فضل لاحظ: صفحات الCSS الفرعية لا ينبغي أن تحتوي على بيانات سرية بما أنها يمكن رؤيتها بواسطة المستخدمين الآخرين.",
        "restrictionsfield-badip": "عنوان أيبي أو نطاق غير صحيح: $1",
index d5cc237..df8fa58 100644 (file)
        "template-protected": "(محميه)",
        "template-semiprotected": "(نص حماية )",
        "hiddencategories": "{{PLURAL:$1|هاد الصفحة ما كايناش فل تصانف المخبّييين|هاد الصفحة كاينة في تصنيف مخبّي واحد|هاد الصفحة كاينة في زوج تاع الـتصانف المخبّيين|هاد الصفحة كاينة في $1 تصنيف مخبّي|هاد الصفحة كاينة في $1 تصنيف مخفبّي|هذه الصفحة كاينة في $1 تصنيف مخبّي}}:",
+       "permissionserrors": "مشكل فل مسموحات",
        "permissionserrorstext-withaction": "ما راكش اوتوريزى ل$2، لل{{PLURAL:$1||سبب هاذا|اسباب هاذي}}:",
        "recreate-moveddeleted-warn": "'''توليه: راك تعاود تصنع باحه اتمحات من قبل.'''\n\nلازم تتأكد بلى الباجه الا نصنعت ماهوش مشكل الا كملت الكتبه فبها.\nريجيستر المحو و النقل معروض هنا باش تراقب :",
        "moveddeleted-notice": "هاذ الباجه تمحات .\nريجيستر المحو والتنقال للباجه معروضين التحت كريفيرونس.",
        "yourrealname": "الاسم الحقاني:",
        "prefs-help-email": "لادريس نتع البريه الإلكترونيه بالخاطر، ولكن هي لازمه في حال نسيت كلمت السر نتاعك.",
        "prefs-help-email-others": "تقدر تاني تخلي لوخرين يتاصلو بيك في باجت نقاشك ولا في وصيله في باجت مستخدم نتاعك, اذا ارسلك واحد ما يبانش لادريس نتاعك , حتى اذ رديت عليه باش يبان لادريس نتاعك.",
+       "group-bot": "بوتات",
+       "grouppage-bot": "{{ns:project}}:بوتات",
        "right-edit": "تبدال الصفحات",
        "right-writeapi": "استعمل API للكتابه نتاع الويكي",
        "newuserlogpage": "ريجيستر صنعة حسابات المستخدمين",
        "rcfilters-filter-editsbyself-label": "التبدال نتاعك",
        "rcfilters-filter-minor-label": "تبديلة خفيفة",
        "rcfilters-filter-major-label": "ماشي تبديلة خفيفة",
-       "rcnotefrom": "التحت التبديلات من <strong>$2</strong> (إلى <strong>$1</strong> معروضة).",
+       "rcnotefrom": "التحت {{PLURAL:$5|تبديلة|التبديلات}}  من <strong>$4 ,$3</strong> (إلى <strong>$1</strong> معروضة).",
        "rclistfrom": "بين التبديلات البديه من $3 $2",
        "rcshowhideminor": "$1 التبديلات الصغير",
        "rcshowhideminor-show": "ورّي",
        "rc-enhanced-expand": "شوف التفاصيل",
        "rc-enhanced-hide": "خبي التفاصيل",
        "recentchangeslinked": "تبديلات مربوطه",
+       "recentchangeslinked-feed": "تبديلات مربوطه",
        "recentchangeslinked-toolbox": "تبديلات الباجات المرتبطه",
        "recentchangeslinked-title": "التبديلات المرتبطة ب \"$1\"",
        "recentchangeslinked-summary": "هاذي ليستة تع التبديلات اللي تمت هاذ الخطرة للباجات الموصولة من باجة معينة (ولا للأعضاء الداخلين في تصنيف معين).\nالصفحات في [[Special:Watchlist|ليستت مراقبة نتاعك]] '''مغلظه'''",
        "nmembers": "$1 اعضاء{{PLURAL:$1||s}}",
        "prefixindex": "كامل الباجات الباديه ب",
        "prefixindex-submit": "ورّي",
+       "listusers": "ليستا تاع المستخدمين",
        "usereditcount": "{{PLURAL:$1|تبديلة|تبديلات}}",
        "usercreated": "{{GENDER:$3|صنعه|صنعته}} في $1 الساعة $2",
        "newpages": "باجه جديده",
        "watchlistfor2": "ل$1 ($2)",
        "watch": "تبع",
        "unwatch": "ما تزيدش تعس",
-       "watchlist-details": "{{PLURAL:$1||باجه وحده|باجتين|$1 باجات|$1 باجه}} في ليستت مراقبتك، من غير اعتبار باجات النقاش هي باجات منفصله.",
+       "watchlist-details": "{{PLURAL:$1||باجه وحده|باجتين|$1 باجات|$1 باجه}} في ليستت مراقبتك، (زدلها باجات النقاش).",
        "wlshowlast": "بين آخر $1 سوايع $2 يامات",
        "watchlist-submit": "ورّي",
        "wlshowhideminor": "تبديلة خفيفة",
        "rollbacklinkcount": "رجّع {{PLURAL:$1|تعديل واحد|$1 تعديلات}}",
        "protectlogpage": "ريجيستر الحمايه",
        "protectedarticle": "راه حمى \"[[$1]]\"",
+       "protect-default": "خلي المستخدمين كامل",
        "restriction-edit": "بدل",
        "undeletelink": "شوف/رجع",
        "undeleteviewlink": "شوف",
        "sp-contributions-search": "تفتاش المشاركات",
        "sp-contributions-username": "عنوان أيبي والال اسم مستخدم:",
        "sp-contributions-toponly": "ما تورّي غير المشاركات التوالا تاع المقالات",
+       "sp-contributions-newonly": "ما تورّي غير المشاركات التوالا تاع المقالات",
        "sp-contributions-submit": "تفتاش",
        "whatlinkshere": "شنوّ يوصّل ل هنا",
        "whatlinkshere-title": "الباجات اللي تقين في \"$1\"",
        "tooltip-undo": "\"نحّي\" فاصي هاد الـمعاودة و حلّ تاقة تاع تبدال بشوفه قبلانيّه. تخلّي باش ترجع لل معاوده التاليه و تزيد الـسبّة علاش فل قابسه تاع الـحويصله.",
        "tooltip-summary": "دخل تلخيص صغير",
        "simpleantispam-label": "مسيّة ضدّ السبام.\nما تعمّرش هادا!",
+       "pageinfo-article-id": "id تاع الصفحة",
+       "pageinfo-robot-index": "تفوت",
        "pageinfo-lastuser": "لخر لي كتب",
        "pageinfo-lasttime": "تاريخ آخر تبديلة",
        "pageinfo-toolboxlink": "معلومات على هاد الصفحة",
        "exif-urgency-normal": "عادي ($1)",
        "namespacesall": "لكل",
        "monthsall": "لكل",
+       "imgmultipagenext": "الباجة الجاية ←",
+       "imgmultigo": "روح",
+       "imgmultigoto": "روح للاباجة $1",
        "watchlisttools-view": "اعرض التبديلات المرتابطه",
        "watchlisttools-edit": "اعرض قائمه المراقبه و عدلها",
        "watchlisttools-raw": "موديفي ليستت التبيعه الخام",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|تقرعيج]])",
        "duplicate-defaultsort": "'''توليه:''' مفتاح التستيف الافتراضي \"$2\" ديباسا مفتاح التستيف الافتراضي التالي\"$1\".",
        "version-no-ext-name": "[بلا اسم]",
+       "redirect-submit": "روح",
+       "redirect-lookup": "حوس في:",
+       "redirect-user": "id تاع المستخدم",
+       "redirect-page": "id تاع الصفحة",
        "specialpages": "الپاجات الخاصّين",
        "external_image_whitelist": " #<pre>خلى هاذ السطر كيما راه\n#حط منثورات التعبيرات المنتظمة (برك الجزء الي يروح بين //) بالتحت\n#هاذ يكون مطابقتها مع مسارات التصاوير البرانيه (الموصولة بصفه مباشره)\n#هاذي الي تشبهغادي تنعرض  كتصاور، خلاف هذا برك وصيلة للتصويرة غادي تنعرض\n#السطور اللي تبدأا ب# تعتبر تعليقات\n#هذا لا يتأثر بحالة الحروف\n\n#حط كامل منثورات التعبيرات المنتظمة فوق هذا السطر. خلي هاذ السطر سواسوا كيما هو</pre>",
        "tag-filter": "صفاية[[Special:Tags|الوشام]]:",
        "tags-active-yes": "إيه",
        "tags-active-no": "لالا",
        "tags-edit": "بدّل",
+       "tags-hitcount": " $1 {{PLURAL:$1|تبديله|تبديلات}}",
        "htmlform-no": "لالا",
        "htmlform-yes": "إيه",
        "logentry-delete-delete": "$1 {{GENDER:$2| راه محا|راهي محات}}الصفحة $3",
        "logentry-upload-upload": " {{GENDER:$2|نزّل|نزّلت}} $1 $3",
        "feedback-error2": "غلطة: تبديلتك ما صلحتش",
        "searchsuggest-search": "فتّش في {{SITENAME}}",
+       "duration-days": "$1 {{PLURAL:$1|يوم|يامات}}",
        "mediastatistics-header-bitmap": "تصويرة Bitmap"
 }
index 520c3ed..81b6d0f 100644 (file)
        "table_pager_limit_submit": "যাওক",
        "table_pager_empty": "ফলাফল নাই",
        "autosumm-blank": "পৃষ্ঠাটো খালী কৰা হ'ল",
-       "autosumm-replace": "পà§\84ষà§\8dঠাà¦\96নক \"$1\"ৰে সলনি কৰা হ'ল",
+       "autosumm-replace": "পà§\83ষà§\8dঠাà¦\9fà§\8bক \"$1\"ৰে সলনি কৰা হ'ল",
        "autoredircomment": "[[$1]]-লৈ পুনৰ্নিৰ্দেশ কৰা হ'ল",
        "autosumm-new": "\"$1\" দি পৃষ্ঠা সৃষ্টি কৰা হ'ল",
        "lag-warn-normal": "$1 {{PLURAL:$1|ছেকেণ্ড|ছেকেণ্ড}} তকৈ নতুন পৰিৱৰ্তনসমূহ এই তালিকাত দেখুওৱা নহবও পাৰে।",
index 65c7dea..87e1e63 100644 (file)
        "prefs-dateformat": "Formatu de data",
        "prefs-timeoffset": "Diferencia horaria",
        "prefs-advancedediting": "Opciones xenerales",
+       "prefs-developertools": "Ferramientes pa desendolcadores",
        "prefs-editor": "Editor",
        "prefs-preview": "Vista previa",
        "prefs-advancedrc": "Opciones avanzaes",
index 5992b8a..1b15090 100644 (file)
        "passwordsent": "Yeni parol \"$1\" üçün qeydiyyata alınan e-poçt ünvanına göndərilmişdir.\nXahiş edirik, e-məktubu aldıqdan sonra yenidən daxil olasınız.",
        "blocked-mailpassword": "Sizin IP-ünvanınız bloklanıb. Sui-istifadənin qarşısını almaq üçün parolun bərpasına icazə verilmir.",
        "eauthentsent": "Göstərilən e-poçt ünvanına məktub göndərildi. \nGələcəkdə həmin ünvana e-məktub ala bilmək üçün, ünvanın sizə aid olmasının təsdiq edilməsi ilə bağlı məktubda verilən göstərişlərə riayət etməlisiniz.",
-       "throttled-mailpassword": "Bir parol sıfırlama e-poçtu son {{PLURAL:$1|bir saat|$1 saat}} içində zatən göndərildi. Xidməti pis niyyətlə istifadə etməyi önləmək üçün, hər {{PLURAL:$1|bir saatda|$1 saatda}} sadəcə bir parol sıfırlama e-poçtu göndəriləcəkdir.",
+       "throttled-mailpassword": "Parol sıfırlama funksiyası son {{PLURAL:$1|bir saat|$1 saat}} ərzində artıq istifadə edilib. Bu xidmətin pis niyyətlə istifadə edilməsinin qarşısını almaq üçün, hər {{PLURAL:$1|bir saatda|$1 saatda}} yalnız bir parol sıfırlama e-məktubu göndərilə bilər.",
        "mailerror": "Məktub göndərmə xətası: $1",
        "acct_creation_throttle_hit": "Sizin IP ünvanınızdan bu vikidə son $2 ərzində {{PLURAL:$1|1 hesab|$1 hesab}} açılmışdır və bu, həmin müddət ərzində icazə verilən maksimum saydır.\nBu səbəbdən, bu IP ünvanı istifadə edən istifadəçilər hal-hazırda başqa hesab aça bilməzlər.",
        "emailauthenticated": "E-poçt ünvanınız $3, $2 tarixində təsdiq edilib.",
        "compareselectedversions": "Seçilən versiyaları müqayisə et",
        "showhideselectedversions": "Seçilən versiyaları göstər/gizlə",
        "editundo": "əvvəlki halına qaytar",
+       "diff-empty": "(Fərqli deyil)",
        "diff-multi-sameuser": "(Eyni istifadəçi tərəfindən edilmiş {{PLURAL:$1|bir dəyişiklik|$1 bir neçə dəyişiklik}} göstərilmir)",
        "diff-multi-manyusers": "({{PLURAL:$2|Bir istifadəçi|$2 istifadəçi}} tərəfindən edilən {{PLURAL:$1|bir ara redaktə|$1 ara redaktə}} göstərilmir)",
        "difference-missing-revision": "Səhifənin  {{PLURAL:$2|bu versiyasının|$2 versiyalarının}} müqayisəsi ($1) tapılmadı.\nBu xəta adətən, köhnəlmiş səhifələrin müqayisə versiyalarından keçid edildikdə baş verir.\nDaha ətraflı məlumat üçün [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} silmə qeydlərinə] baxın.",
        "search-redirect": "($1 səhifəsindən yönləndirmə)",
        "search-section": "(bölmə $1)",
        "search-category": "(kateqoriya $1)",
+       "search-file-match": "(faylın məzmunu ilə oxşardır)",
        "search-suggest": "Bəlkə, bunu nəzərdə tuturdunuz: $1",
        "search-interwiki-caption": "Qonşu layihələrdəki nəticələr",
        "search-interwiki-default": "$1 nəticələri:",
        "tooltip-pt-login": "Daxil olmanız tövsiyə olunur, amma bu məcburi tələb deyil.",
        "tooltip-pt-logout": "Sistemdən çıx",
        "tooltip-ca-talk": "Məqalə haqqındə müzakirə edib, münasibətivi bildir",
-       "tooltip-ca-edit": "Bu səhifəni redaktə edə bilərsiniz. Lütfən əvvəlcə sınaq gostərişi edin.",
+       "tooltip-ca-edit": "Bu səhifəni redaktə et",
        "tooltip-ca-addsection": "Yeni bölmə yarat",
        "tooltip-ca-viewsource": "Bu səhifə dəyişikliklərdən mühafizə olunur. Amma siz onun mətninə baxa və mətnin surətini köçürə bilərsiniz.",
        "tooltip-ca-history": "Bu səhifənin keçmiş nüsxələri.",
        "tooltip-t-recentchangeslinked": "Bu məqaləyə aid başqa səhifələrdə yeni dəyişikliklər",
        "tooltip-feed-rss": "Bu səhifə üçün RSS yayımı",
        "tooltip-feed-atom": "Bu səhifə üçün Atom yayımı",
-       "tooltip-t-contributions": "Bu istifadəçinin redaktə etdiyi səhifələrin siyahısı",
+       "tooltip-t-contributions": "{{GENDER:$1|this user}} adlı istifadəçinin redaktə etdiyi səhifələrin siyahısı",
        "tooltip-t-emailuser": "{{GENDER:$1|Bu istifadəçiyə}} e-məktub göndər",
        "tooltip-t-upload": "Yeni şəkil və ya multimedia faylı yüklə",
        "tooltip-t-specialpages": "Xüsusi səhifələrin siyahısı",
        "pageinfo-visiting-watchers": "Səhifəni izləmədə saxlayanlardan son dəyişiklikləri görənlərin sayı",
        "pageinfo-few-watchers": "$1 {{PLURAL:$1|izləyicidən|izləyicilərdən}} az",
        "pageinfo-redirects-name": "Bu səhifəyə yönləndirmələrin sayı",
+       "pageinfo-subpages-name": "Bu səhifənin alt-səhifələrinin sayı",
        "pageinfo-firstuser": "Səhifəni yaradan",
        "pageinfo-firsttime": "Səhifənin yaranma tarixi",
        "pageinfo-lastuser": "Sonuncu redaktor",
        "version-entrypoints-header-url": "URL",
        "version-libraries-library": "Kitabxana",
        "version-libraries-authors": "Müəlliflər",
+       "redirect-submit": "Keç",
+       "redirect-lookup": "Bax",
        "redirect-user": "İstifadəçi ID-si",
        "redirect-page": "Səhifənin identifikatoru",
+       "redirect-revision": "Səhifənin versiyası",
+       "redirect-file": "Fayl adı",
        "fileduplicatesearch": "Dublikat fayl axtarışı",
        "fileduplicatesearch-filename": "Fayl adı:",
        "fileduplicatesearch-submit": "Axtar",
index c609322..29e7750 100644 (file)
        "rcfilters-filter-user-experience-level-registered-label": "Зарэгістраваныя",
        "rcfilters-filter-user-experience-level-registered-description": "Рэдактары, якія ўвайшлі ў сыстэму.",
        "rcfilters-filter-user-experience-level-unregistered-label": "Незарэгістраваныя",
-       "rcfilters-filter-user-experience-level-unregistered-description": "Рэдактары, якія не ўвайшлі ў сыстэму",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Рэдактары, якія не ўвайшлі ў сыстэму.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Навічкі",
        "rcfilters-filter-user-experience-level-newcomer-description": "Зарэгістраваныя рэдактары, якія маюць менш чым 10 правак ці 4 дні актыўнасьці.",
        "rcfilters-filter-user-experience-level-learner-label": "Вучні",
        "boteditletter": "р",
        "number_of_watching_users_pageview": "[$1 {{PLURAL:$1|назіральнік|назіральнікі|назіральнікаў}}]",
        "rc-change-size-new": "$1 {{PLURAL:$1|байт|байты|байтаў}} пасьля зьмены",
-       "newsectionsummary": "/* $1 */ новая сэкцыя",
+       "newsectionsummary": "/* $1 */ новы разьдзел",
        "rc-enhanced-expand": "Паказаць падрабязнасьці",
        "rc-enhanced-hide": "Схаваць падрабязнасьці",
        "rc-old-title": "зь першапачатковай назвай «$1»",
        "recentchangeslinked-feed": "Зьвязаныя праўкі",
        "recentchangeslinked-toolbox": "Зьвязаныя праўкі",
        "recentchangeslinked-title": "Зьвязаныя праўкі для «$1»",
-       "recentchangeslinked-summary": "Увядзіце назву старонкі, каб пабачыць зьмены старонак, на якія яна спасылаецца або якія спасылаюцца на яе (каб пабачыць старонкі з катэгорыі, увядзіце Катэгорыя:Назва). Зьмены старонак з [[Special:Watchlist|вашага сьпісу назіраньня]] пазначаныя <strong>тоўстым шрыфтам</strong>.",
+       "recentchangeslinked-summary": "Увядзіце назву старонкі, каб пабачыць зьмены старонак, на якія яна спасылаецца або якія спасылаюцца на яе (каб пабачыць старонкі з катэгорыі, увядзіце {{ns:category}}:Назва). Зьмены старонак з [[Special:Watchlist|вашага сьпісу назіраньня]] пазначаныя <strong>тоўстым шрыфтам</strong>.",
        "recentchangeslinked-page": "Назва старонкі:",
        "recentchangeslinked-to": "Замест гэтага паказваць зьмены на старонках, што спасылаюцца на гэтую старонку",
        "recentchanges-page-added-to-category": "[[:$1]] дададзеная да катэгорыі",
        "version-specialpages": "Спэцыяльныя старонкі",
        "version-parserhooks": "Працэдуры-перахопнікі парсэра",
        "version-variables": "Зьменныя",
+       "version-editors": "Рэдактары",
        "version-antispam": "Абарона ад спаму",
        "version-api": "API",
        "version-other": "Іншыя",
        "tag-mw-rollback": "Адкат",
        "tag-mw-rollback-description": "Праўкі, якія адкатваюць папярэднія рэдагаваньні з дапамогай спасылкі адкату",
        "tag-mw-undo": "Адмена",
-       "tag-mw-undo-description": "Праўкі, якія адмяняць папярэднія праўкі з дапамогай спасылкі адмены праўкі",
+       "tag-mw-undo-description": "Ð\9fÑ\80аÑ\9eкÑ\96, Ñ\8fкÑ\96Ñ\8f Ð°Ð´Ð¼Ñ\8fнÑ\8fÑ\8eÑ\86Ñ\8c Ð¿Ð°Ð¿Ñ\8fÑ\80Ñ\8dднÑ\96Ñ\8f Ð¿Ñ\80аÑ\9eкÑ\96 Ð· Ð´Ð°Ð¿Ð°Ð¼Ð¾Ð³Ð°Ð¹ Ñ\81паÑ\81Ñ\8bлкÑ\96 Ð°Ð´Ð¼ÐµÐ½Ñ\8b Ð¿Ñ\80аÑ\9eкÑ\96",
        "tags-title": "Меткі",
        "tags-intro": "На гэтай старонцы знаходзіцца сьпіс метак, якімі праграмнае забесьпячэньне можа пазначыць рэдагаваньне, і іх значэньне.",
        "tags-tag": "Назва меткі",
        "unlinkaccounts-success": "Рахунак быў адлучаны.",
        "authenticationdatachange-ignored": "Зьмена зьвестак аўтэнтыфікацыі не была апрацаваная. Магчыма, ня быў наладжаны правайдэр?",
        "userjsispublic": "Калі ласка, заўважце: падстаронкі JavaScript ня могуць утрымліваць канфідэнцыйныя зьвесткі, бо яны бачныя іншым удзельнікам.",
+       "userjsonispublic": "Калі ласка, заўважце: JSON-падстаронкі не павінныя ўтрымліваць канфідэнцыйныя зьвесткі, бо яны могуць быць прагледжаныя іншымі ўдзельнікамі.",
        "usercssispublic": "Калі ласка, заўважце: падстаронкі CSS не павінны ўтрымліваць канфідэнцыйныя зьвесткі, бо яны бачныя іншым удзельнікам.",
        "restrictionsfield-badip": "Няслушны IP-адрас ці дыяпазон: $1",
        "restrictionsfield-label": "Дазволеныя IP-дыяпазоны:",
index 0e0fdb1..0333b61 100644 (file)
@@ -38,8 +38,8 @@
        },
        "tog-underline": "Падкрэсліваць спасылкі:",
        "tog-hideminor": "Не паказваць дробныя праўкі",
-       "tog-hidepatrolled": "Без паказу ўхваленых правак у нядаўніх змяненнях",
-       "tog-newpageshidepatrolled": "Без паказу ўхваленых правак у пераліку новых старонак",
+       "tog-hidepatrolled": "Без паказу дагледжаных правак у апошніх зменах",
+       "tog-newpageshidepatrolled": "Без паказу дагледжаных правак у пераліку новых старонак",
        "tog-hidecategorization": "Схаваць катэгорызацыю старонак",
        "tog-extendwatchlist": "Паказваць усе змяненні, а не толькі апошнія",
        "tog-usenewrc": "Групаваць змены па старонках у апошніх зменах і спісе назірання",
@@ -63,7 +63,7 @@
        "tog-shownumberswatching": "Паказваць колькасць назіральнікаў",
        "tog-oldsig": "Ваш цяперашні подпіс:",
        "tog-fancysig": "Апрацоўваць подпіс як вікі-тэкст (без аўтаматычнай спасылкі)",
-       "tog-uselivepreview": "Паказваць папярэдні прагля без перазагрузкі старонкі",
+       "tog-uselivepreview": "Паказваць папярэдні прагляд без перазагрузкі старонкі",
        "tog-forceeditsummary": "Папярэджваць пра пустое поле тлумачэння праўкі",
        "tog-watchlisthideown": "Не паказваць маіх правак са спісу назірання",
        "tog-watchlisthidebots": "Не паказваць праўкі ботаў са спісу назірання",
@@ -75,7 +75,7 @@
        "tog-watchlisthidepatrolled": "Не паказваць ухваленых правак у старонках са спісу назірання",
        "tog-watchlisthidecategorization": "Схаваць катэгорызацыю старонак",
        "tog-ccmeonemails": "Слаць мне копіі маіх лістоў",
-       "tog-diffonly": "Не паказваць рэшты старонкі пад розніцай",
+       "tog-diffonly": "Не паказваць змест старонкі пад параўнаннем двух версій",
        "tog-showhiddencats": "Паказаць схаваныя катэгорыі",
        "tog-norollbackdiff": "Не паказваць розніцу ў выніку адкату",
        "tog-useeditwarning": "Папярэдзіць мяне, калі я пакідаю старонку з незахаванымі праўкамі",
        "confirmable-no": "Не",
        "thisisdeleted": "Паказаць ці аднавіць $1?",
        "viewdeleted": "Ці паказаць $1?",
-       "restorelink": "$1 {{PLURAL:$1|сцёртая праўка|сцёртыя праўкі|сцёртых правак}}",
+       "restorelink": "$1 {{PLURAL:$1|выдаленую праўку|выдаленыя праўкі|выдаленых правак}}",
        "feedlinks": "Струмень:",
        "feed-invalid": "Недапушчальны тып струмяня навін.",
        "feed-unavailable": "Няма струмянёў навін",
        "prefs-dateformat": "Фармат даты",
        "prefs-timeoffset": "Часавы пояс",
        "prefs-advancedediting": "Агульныя настройкі",
+       "prefs-developertools": "Інструменты распрацоўшчыка",
        "prefs-editor": "Рэдактар",
        "prefs-preview": "Перадпаказ",
        "prefs-advancedrc": "Пашыраныя настройкі",
        "rcfilters-filter-user-experience-level-newcomer-label": "Навічкі",
        "rcfilters-filter-user-experience-level-newcomer-description": "Зарэгістраваныя рэдактары, якія маюць менш за 10 правак або 4 дзён актыўнасці.",
        "rcfilters-filter-user-experience-level-learner-label": "Вучні",
-       "rcfilters-filter-user-experience-level-learner-description": "Ð\97аÑ\80Ñ\8dгÑ\96Ñ\81Ñ\82Ñ\80аванÑ\8bÑ\8f Ñ\80Ñ\8dдакÑ\82аÑ\80Ñ\8b, Ð²Ð¾Ð¿Ñ\8bÑ\82 Ñ\8fкÑ\96Ñ\85 Ð·Ð½Ð°Ñ\85одзÑ\96Ñ\86Ñ\86а Ð¿Ð°Ð¼Ñ\96ж Â«Ð½Ð°Ð²Ñ\96Ñ\87камÑ\96» Ñ\96 Â«Ð´Ð°Ñ\81ведÑ\87анÑ\8bмÑ\96 Ñ\83дзельнікамі».",
+       "rcfilters-filter-user-experience-level-learner-description": "Ð\97аÑ\80Ñ\8dгÑ\96Ñ\81Ñ\82Ñ\80аванÑ\8bÑ\8f Ñ\80Ñ\8dдакÑ\82аÑ\80Ñ\8b, Ð²Ð¾Ð¿Ñ\8bÑ\82 Ñ\8fкÑ\96Ñ\85 Ð·Ð½Ð°Ñ\85одзÑ\96Ñ\86Ñ\86а Ð¿Ð°Ð¼Ñ\96ж Â«Ð½Ð°Ð²Ñ\96Ñ\87камÑ\96» Ñ\96 Â«Ð²Ð¾Ð¿Ñ\8bÑ\82нÑ\8bмÑ\96 Ñ\9eдзельнікамі».",
        "rcfilters-filter-user-experience-level-experienced-label": "Вопытныя ўдзельнікі",
        "rcfilters-filter-user-experience-level-experienced-description": "Зарэгістраваныя рэдактары з больш за 500 правак і 30 дзён актыўнасці.",
        "rcfilters-filtergroup-automated": "Аўтаматызаваны ўнёсак",
        "rcfilters-filter-major-description": "Праўкі, не пазначаныя як дробныя.",
        "rcfilters-filtergroup-watchlist": "Старонкі ў спісе назірання",
        "rcfilters-filter-watchlist-watched-label": "У спісе назірання",
-       "rcfilters-filter-watchlist-watched-description": "Змяненні старонак у Вашым спісе назірання.",
-       "rcfilters-filter-watchlist-watchednew-label": "Новыя змяненні ў спісе назірання",
+       "rcfilters-filter-watchlist-watched-description": "Змены ў старонках з Вашага спісу назірання.",
+       "rcfilters-filter-watchlist-watchednew-label": "Новыя змены ў спісе назірання",
        "rcfilters-filter-watchlist-watchednew-description": "Праўкі на старонках з Вашага спісу назірання, якія Вы не праглядалі з часу іх здзяйснення.",
        "rcfilters-filter-watchlist-notwatched-label": "Не ў спісе назірання",
        "rcfilters-filter-watchlist-notwatched-description": "Усё за выключэннем правак старонак з Вашага спісу назірання.",
        "rcfilters-liveupdates-button-title-off": "Паказваць навыя змены адразу пасля іх з’яўлення",
        "rcfilters-watchlist-markseen-button": "Пазначыць усе змены як прагледжаныя",
        "rcfilters-watchlist-edit-watchlist-button": "Рэдагаваць Ваш спіс назіраных старонак",
+       "rcfilters-watchlist-showupdated": "Змены старонак, якія Вы не наведвалі з таго часу, як яны адбыліся, выдзелены <strong>тлустым</strong> і пазначаны маркерам.",
        "rcfilters-preference-label": "Схаваць палепшаную версію «Апошніх змен»",
        "rcfilters-preference-help": "Адкатвае рэдызайн інтэрфейсу 2017 года і ўсе інструменты, дададзеныя з тых часоў.",
        "rcnotefrom": "Ніжэй {{PLURAL:$5|паказана змяненне|паказаны змены}} з <strong>$3, $4</strong> (не больш за <strong>$1</strong>).",
        "tag-filter-submit": "Фільтр",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Тэг|Тэгі}}]]: $2)",
        "tag-mw-contentmodelchange": "змена мадэлі змесціва",
+       "tag-mw-contentmodelchange-description": "Праўкі, якія [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel мяняюць мадэль змесціва] старонкі",
        "tag-mw-new-redirect": "Новая перасылка",
+       "tag-mw-new-redirect-description": "Праўкі, якімі створаны перасылкі або старонкі заменены на перасылкі",
        "tag-mw-removed-redirect": "Выдаленае перанакіраванне",
-       "tag-mw-changed-redirect-target": "Змяненне мэты перенаправления",
+       "tag-mw-removed-redirect-description": "Праўкі, якія замяняюць існуючую перасылку на не-перасылку",
+       "tag-mw-changed-redirect-target": "Змяненне мэты перасылкі",
+       "tag-mw-changed-redirect-target-description": "Праўкі, якія змяняюць мэту перасылкі",
        "tag-mw-blank": "ачыстка",
-       "tag-mw-blank-description": "Праўкі, ачышчальныя гэтую старонку",
+       "tag-mw-blank-description": "Праўкі, якія ачышчаюць старонку",
        "tag-mw-replace": "Заменена",
-       "tag-mw-rollback": "адкат",
-       "tag-mw-undo": "адмена",
+       "tag-mw-replace-description": "Праўкі, якія выдаляюць больш за 90% зместу старонкі",
+       "tag-mw-rollback": "Адкат",
+       "tag-mw-rollback-description": "Праўкі, якія адкатваюць папярэднія праўкі з дапамогай спасылкі адкату",
+       "tag-mw-undo": "Адмена",
+       "tag-mw-undo-description": "Праўкі, якія адмяняюць папярэднія праўкі з дапамогай спасылкі адмены праўкі",
        "tags-title": "Біркі",
        "tags-intro": "Тут пералічаныя біркі, якімі праграмы могуць пазначыць праўку, а таксама іх значэнні.",
        "tags-tag": "Назва біркі",
        "mw-widgets-mediasearch-input-placeholder": "Пошук мультымедыя",
        "mw-widgets-titleinput-description-new-page": "старонка яшчэ не існуе",
        "mw-widgets-titleinput-description-redirect": "перанакіраванне на $1",
+       "mw-widgets-usersmultiselect-placeholder": "Дадаць яшчэ...",
        "date-range-from": "Ад даты:",
        "date-range-to": "Да даты:",
        "randomrootpage": "Выпадковая карэнная старонка",
index 21604ef..c2970ef 100644 (file)
        "savechanges": "Съхраняване на промените",
        "publishpage": "Публикуване на страницата",
        "publishchanges": "Публикуване на промените",
+       "publishchanges-start": "Публикуване на промените...",
        "preview": "Предварителен преглед",
        "showpreview": "Предварителен преглед",
        "showdiff": "Показване на промените",
index 6d19efd..5f90062 100644 (file)
@@ -44,6 +44,7 @@
        "tog-watchlisthideminor": "धियानसूची से छोट संपादन छिपावल जाय",
        "tog-watchlisthideliu": "खाता में प्रवेश भइल प्रयोगकर्ता लोग के संपादन धियानसूची से छिपावल जाय",
        "tog-watchlistreloadautomatically": "जब कौनों फिल्टर बदलल जाय तब धियानसूची ऑटोमेटिक दोबारा लोड होखे (जावास्क्रिप्ट जरूरी)",
+       "tog-watchlistunwatchlinks": "धियान में राखल जवना पन्ना सभ में बदलाव भइल बा उनहन में बिनाधियान/धियान के चीन्हा सभ ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) के सीधे जोड़ीं (एह टॉगल सुबिधा खातिर जावास्क्रिप्ट के जरूरत पड़ी)",
        "tog-watchlisthideanons": "बेनाम प्रयोगकर्ता लोग के संपादन धियानसूची से छिपावल जाय",
        "tog-watchlisthidepatrolled": "जाँचल गइल संपादन के धियानसूची से छिपावल जाय",
        "tog-watchlisthidecategorization": "पन्ना श्रेणीकरण छिपावल जाय",
        "cascadeprotected": "ए पन्ना के संपादन कइल सुरक्षित क दिहल गइल बा काहें कि ई {{PLURAL:$1|पन्ना में, जौना के|पन्ना सब में, जिन्हन के}} \"कैस्केडिंग\" (बिस्तारित) सुरक्षा चालू क के सुरक्षित कइल गइल बा, में समाइल बाटे:\n$2",
        "namespaceprotected": "रउआ के '''$1''' नामस्थान के पन्नं में सम्पादन करे के अधिकार नइखे दिहल गइल।",
        "customcssprotected": "रउआ के इ CSS पन्ना के संपादित करे के अनुमति नइखे, काहे कि इ में अन्य सदस्यं के व्यक्तिगत सेटिंग्स समाविष्ट बा।",
+       "customjsonprotected": "रउआ के एह JSON पन्ना के संपादित करे के इजाजत नइखे, काहें कि एह में दुसरे प्रयोगकर्ता ब्यक्तिगत सेटिंग सामिल बा।",
        "customjsprotected": "रउआ इ जावास्क्रिप्ट पन्ना के संपादित करे के अनुमति नइखे, काहे कि इ में अन्य सदस्यं के व्यक्तिगत सेटिंग्स समाविष्ट बा।",
        "mycustomcssprotected": "रउआ इ CSS के पन्ना के सम्पादित करे के अधिकार नइखे।",
+       "mycustomjsonprotected": "आपके एह JSON पन्ना में संपादन के इजाजत नइखे।",
        "mycustomjsprotected": "रउआ इ जावास्क्रिप्ट पन्ना के सम्पादित करे के अधिकार नइखे।",
        "myprivateinfoprotected": "रउआ लगे आपन व्यक्तिगत जानकारी बदले के अनुमति नइखे।",
        "mypreferencesprotected": "रउआ लगे आपन वरियतां ‍‍‍‍(पसंद) बदले के अधिकार नइखे।",
        "wrongpasswordempty": "गुप्तशब्द खाली बा। कृपया फिर से कोसिस करीं।",
        "passwordtooshort": "गुप्तशब्द कम से कम {{PLURAL:$1|1 अक्षर|$1 अक्षर}} के होवे के चाहीं।",
        "passwordtoolong": "गुप्तशब्द {{PLURAL:$1|$1 अक्षर}} से लमहर ना चाहीं।",
-       "passwordtoopopular": "à¤\85à¤\95à¥\8dसरहा à¤¬à¥\80à¤\9bल à¤\9cाà¤\8f à¤µà¤¾à¤²à¤¾ à¤\97à¥\81पà¥\8dतशबà¥\8dद à¤¨à¤¾ à¤\87सà¥\8dतà¥\87माल à¤¹à¥\8b à¤¸à¤\95à¥\87 à¤²à¤¾à¥¤ à¤\95à¥\8cनà¥\8bà¤\82 à¤\85à¤\89रà¥\80 à¤\96ास à¤\85लà¤\97 à¤\95िसिम à¤\95à¥\87 à¤\97à¥\81पà¥\8dतशबà¥\8dद à¤\9aà¥\81नà¥\80à¤\82।",
+       "passwordtoopopular": "à¤\85à¤\95à¥\8dसरहा à¤¬à¥\80à¤\9bल à¤\9cाà¤\8f à¤µà¤¾à¤²à¤¾ à¤\97à¥\81पà¥\8dतशबà¥\8dद à¤¨à¤¾ à¤\87सà¥\8dतà¥\87माल à¤\95à¤\87ल à¤\9cा à¤¸à¤\95à¥\87 à¤²à¤¾à¥¤ à¤\85à¤\87सन à¤\97à¥\81पà¥\8dतशबà¥\8dद à¤¬à¥\80à¤\9bà¥\80à¤\82 à¤\9cà¥\87à¤\95र à¤\85à¤\82à¤\9cाद à¤²à¤\97ावल à¤¢à¥\87र à¤\95ठिन à¤¹à¥\8bà¤\96à¥\87।",
        "password-name-match": "राउर गुप्तशब्द राउर प्रयोगकर्तानाँव से अलग होखे के चाहीं।",
        "password-login-forbidden": "इस प्रयोगकर्तानाँव आ गुप्तशब्द के प्रयोग वर्जित बा।",
        "mailmypassword": "गुप्तशब्द रिसेट करीं",
        "passwordremindertitle": "{{SITENAME}} खातिर नया अस्थायी गुप्तशब्द",
-       "passwordremindertext": "केहू (शायद रउए, $1 आइपी पता से) {{SITENAME}} ($4) पर प्रयोग खातिर नया गुप्तशब्द के निवेदन कइले बा। प्रयोगकर्ता \"$2\" खातिर एगो अस्थायी गुप्तशब्द बना दिहल गइल बा, आ ई \"$3\" बा। यदि ई रउवें चाहत रहलीं, त अब रउआँ के खाता में प्रवेश क के एगो नया गुप्तशब्द चुने के पड़ी।\nराउर अस्थायी गुप्तशब्द के अवधि {{PLURAL:$5|एक दिन|$5 दिन}} में खतम हो जाई।\n\nयदि ई निवेदन केहु अउर कइले रहल, या रउआँ के आपन पुरनका गुप्तशब्द इयाद आ गइल बा आ बदलाव नइखीं चाहत, त रउआँ ए सनेसा के अनदेखा कर सकत बानी, आ आपन पुरनका गुप्तशब्द के प्रयोग पहिले नियर कर सकत बानी।",
+       "passwordremindertext": "केहू ($1 आइपी पता से) {{SITENAME}} ($4) पर प्रयोग खातिर नया गुप्तशब्द के निवेदन कइले बा। प्रयोगकर्ता \"$2\" खातिर एगो अस्थायी गुप्तशब्द बना दिहल गइल बा, आ ई \"$3\" बा। यदि ई रउवें चाहत रहलीं, त अब रउआँ के खाता में प्रवेश क के एगो नया गुप्तशब्द चुने के पड़ी।\nराउर अस्थायी गुप्तशब्द के समयसीमा {{PLURAL:$5|एक दिन|$5 दिन}} में खतम हो जाई।\n\nयदि ई निवेदन केहु अउर कइले रहल, या रउआँ के आपन पुरनका गुप्तशब्द इयाद आ गइल बा आ बदलाव नइखीं चाहत, त रउआँ ए सनेसा के अनदेखा कर सकत बानी, आ आपन पुरनका गुप्तशब्द के प्रयोग पहिले नियर कर सकत बानी।",
        "noemail": "\"$1\" प्रयोगकर्ता खातिर कौनों ईमेल पता रिकार्ड में नइखे।",
        "noemailcreate": "रउआँ के एगो जायज ईमेल पता देवे के पड़ी।",
        "passwordsent": "\"$1\" के ईमेल पता पर एगो नया गुप्तशब्द भेज दिहल गइल बा।\nईमेल पावे के बाद कृपया दुबारा खाता में प्रवेश करीं।",
        "savechanges": "बदलाव सहेजीं",
        "publishpage": "पन्ना प्रकाशित करीं",
        "publishchanges": "बदलाव प्रकाशित करीं",
+       "savearticle-start": "पन्ना सहेजीं...",
+       "savechanges-start": "बदलाव सहेजीं...",
+       "publishpage-start": "पन्ना प्रकाशित करीं...",
+       "publishchanges-start": "बदलाव प्रकाशित करीं...",
        "preview": "झलक",
        "showpreview": "झलक देखीं",
        "showdiff": "बदलाव देखीं",
        "noarticletext-nopermission": "ए पन्ना मे अभी कौनों सामग्री नइखे।\nरउआँ दुसरा पन्ना में [[Special:Search/{{PAGENAME}}|ए टाइटिल के खोज]] कर सकत बानीं,\nया <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} या संबंधित लॉग खोज सकत बानी]</span>, बाकी रउआ के ई पन्ना बनावे के परमीशन नइखे।",
        "missing-revision": "\"{{FULLPAGENAME}}\" पन्ना के संशोधन #$1 उपलब्ध नइखे।\n\nसाधारण रुप से इ एगो हटावल गइल पन्ना के पुरान लिंक पर क्लिक कइला से होखेला।\nअधिक जानकारी खातिर आप [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} हटावे के लॉग] देख सकत बानी।",
        "userpage-userdoesnotexist": "सदस्य खाता \"$1\" पंजीकृत नइखे।\nकृपया जाँच लीं कि आप इ पन्ना संपादित अथवा निर्मित करे के चाहत बानी कि ना।",
-       "userpage-userdoesnotexist-view": "सदसà¥\8dय à¤\96ाता \"$1\" à¤ªà¤\82à¤\9cà¥\80à¤\95à¥\83त à¤¨à¤\88à¤\96à¥\87 à¤­à¤\88ल।",
+       "userpage-userdoesnotexist-view": "पà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤\96ाता \"$1\" à¤°à¤\9cिसà¥\8dà¤\9fरà¥\8dड à¤¨à¤\87à¤\96à¥\87 à¤­à¤\87ल।",
        "blocked-notice-logextract": "ई प्रयोगकर्ता के ई समय निष्क्रीय कर दिहल गईल बा।\nनविनतम नष्ट लौग प्रविष्टी उद्धरण खातिर निचे दिहल बा:",
        "clearyourcache": "<strong>नोट:</strong> सहेजे के बाद, बदलाव देखे खातिर आपके अपने ब्राउजर के कैशे खाली करे के पड़ सकत बा।\n* <strong>फायरफॉक्स / सफारी:</strong><em>शिफ्ट</em> दबा के <em>रीलोड</em> पर क्लिक करीं, या फिर <em>Ctrl-F5</em> या <em>Ctrl-R</em> दबाईं (मैक पर <em>⌘-R</em>)\n* <strong>गूगल क्रोम:</strong> <em>Ctrl-Shift-R</em> दबाईं (मैक पर <em>⌘-Shift-R</em>)\n* <strong>इंटरनेट एक्स्प्लोरर:</strong> <em>Ctrl</em> दबा के  <em>Refresh</em> पर क्लिक करीं, या <em>Ctrl-F5</em> दबईं\n* <strong>ओपेरा:</strong> <em>Menu → Settings</em> में जाईं (मैक में <em>Opera → Preferences</em>) आ एकरे बाद <em>Privacy & security → Clear browsing data → Cached images and files</em> क्लिक करीं।",
        "usercssyoucanpreview": "<strong>टिप:</strong> आपन नया CSS के टेस्ट करे खातिर सहेजे से पहिले \"{{int:showpreview}}\" बटन के प्रयोग करीं।",
+       "userjsonyoucanpreview": "<strong>टिप:</strong> आपन नया JSON के टेस्ट करे खातिर सहेजे से पहिले \"{{int:showpreview}}\" बटन के प्रयोग करीं।",
        "userjsyoucanpreview": "<strong>टिप:</strong> आपन नया जावास्क्रिप्ट के टेस्ट करे खातिर सहेजे से पहिले \"{{int:showpreview}}\" बटन के प्रयोग करीं।",
        "usercsspreview": "<strong>याद रहे की आप अपनी सदस्य CSS के खाली नमूना भर देखत बानी।\nई अबहिन ले सहेजल ना गइल बाटे।</strong>",
+       "userjsonpreview": "<strong>याद रहे की आप अपने JSON config के खाली टेस्ट करत बानी/नमूना देखत बानी।\nई अबहिन सहेजल ना गइल बाटे।</strong>",
        "userjspreview": "<strong>याद रहे की आप अपनी सदस्य जावास्क्रिप्ट के खाली टेस्ट करत बानी/नमूना देखत बानी।\nई अबहिन सहेजल ना गइल बाटे।</strong>",
        "sitecsspreview": "<strong>याद रहे की आप ए CSS क खाली नमूना देखत बानी।\nई अबहिन ले सहेजल ना गइल बा!</strong>",
+       "sitejsonpreview": "<strong>याद रहे की आप ए JSON config क खाली नमूना देखत बानी।\nई अबहिन ले सहेजल ना गइल बा!</strong>",
        "sitejspreview": "<strong>याद रहे की आप ए जावास्क्रिप्ट कोड क खाली नमूना देखत बानी।\nई अबहिन ले सहेजल ना गइल बा!</strong>",
-       "userinvalidconfigtitle": "<strong>चेतावनी:</strong> कौनों skin \"$1\"नइखे।\nCustom .css आ .js पन्ना सभ छोटका अक्षर में टाइटिल इस्तेमाल करे लें जइसे की, {{ns:user}}:Foo/vector.css ना की {{ns:user}}:Foo/Vector.css।",
+       "userinvalidconfigtitle": "<strong>चेतावनी:</strong> कौनों skin \"$1\"नइखे।\nCustom .css, ,json, आ .js पन्ना सभ छोटका अक्षर में टाइटिल इस्तेमाल करे लें जइसे की, {{ns:user}}:Foo/vector.css ना की {{ns:user}}:Foo/Vector.css",
        "updated": "(अपडेट करल गईल)",
        "note": "'''सूचना:'''",
-       "previewnote": "'''याद रखीं, इ एगो झलक मात्र हो।'''\nराउर बदलाव अभी तक सुरक्षित नईखे करल गईल!",
+       "previewnote": "<strong>याद रखीं, इ एगो झलक भर हवे।</strong>\nराउर बदलाव अभिन सहेजल ना गइल बा!",
        "continue-editing": "संपादन क्षेत्र में जाईं",
        "previewconflict": "ई नमूना ई देखावत बा की अगर रउआँ ए संपादन बक्सा में मौजूद पाठ के सहेजब त ऊ कइसन देखाई पड़ी।",
        "session_fail_preview": "माफ करीं! एह सत्र के आँकड़ा के गायब हो गइला के कारण आपके संपादन के प्रॉसेस करे में हमनी के असमर्थ बानी जा।\nहो सकेला आप लॉगआउट हो गइल होखीं।\n<strong>जाँच लेईं कि आप अभी लॉगिन बानी आ दुबारा कोसिस करीं</strong>।\nअगर तबो काम ना होखे तब [[Special:UserLogout|लॉगआउट कइके]] आ दोबारा लॉग इन कइ के कोसिस करी, आ जाँच करीं कि आपके ब्राउजर एह साइट से कुकीज सभ के मंजूर करत बा।",
        "longpageerror": "<strong>खराबी: आप जवन पाठ लिख के दिहले बानी ऊ {{PLURAL:$1|एक किलोबाइट|$1 किलोबाइट्स}} के बाटे, जेवन अधिकतम सीमा {{PLURAL:$2|एक किलोबाइट|$2 किलोबाइट्स}} से ढेर बा।</strong>\nई सहेजल ना जा सकेला।",
        "readonlywarning": "<strong>चेतावनी: एह समय मरम्मत खातिर डेटाबेस लॉक कइल गइल बा, एही कारन आप तुरंते एही समय आपन संपादन ना सहेज पाइब।</strong>\nरउआँ अपनी पाठ (टेक्स्ट) के कौनों पाठ फाइल (टेक्स्ट फाइल) में बाद खातिर सहेज के रख लीं।\n\nजे सिस्टम प्रबंधक एकरा के लॉक कइले बा ऊ नीचे लिखल कारण दिहले बा: $1",
        "protectedpagewarning": "<strong>चेतावनी: ई पन्ना सुरक्षित कइल गइल बा जेवना से कि एकरा के खाली प्रबंधक (Admin) विशेषाधिकार वाला सदस्य लोग संपादित क सकत बा।</strong>\nप्रसंग बूझे खातिर सबसे नया लॉग एंट्री नीचे दिहल जात बा:",
-       "semiprotectedpagewarning": "<strong>नà¥\8bà¤\9f:</strong> à¤\88 à¤ªà¤¨à¥\8dना à¤¸à¥\81रà¤\95à¥\8dषित à¤\95à¤\87ल à¤\97à¤\87ल à¤¬à¤¾ à¤\95ि à¤\8fà¤\95रा à¤\95à¥\87 à¤\96ालà¥\80 à¤°à¤\9cिसà¥\8dà¤\9fरà¥\8dड à¤¸à¤¦à¤¸à¥\8dय à¤²à¥\8bà¤\97 à¤¸à¤\82पादित à¤\95 à¤¸à¤\95त à¤¬à¤¾à¥¤\nसभसà¥\87 à¤¨à¤¯à¤¾ à¤²à¥\89à¤\97 à¤\8fà¤\82à¤\9fà¥\8dरà¥\80 à¤¨à¥\80à¤\9aà¥\87 à¤ªà¥\8dरसà¤\82à¤\97 à¤¬à¤¤à¤¾à¤µà¥\87 à¤\96ातिर दिहल जात बा:",
+       "semiprotectedpagewarning": "<strong>नà¥\8bà¤\9f:</strong> à¤\88 à¤ªà¤¨à¥\8dना à¤¸à¥\81रà¤\95à¥\8dषित à¤\95à¤\87ल à¤\97à¤\87ल à¤¬à¤¾ à¤\95ि à¤\8fà¤\95रा à¤\95à¥\87 à¤\96ालà¥\80 à¤\91à¤\9fà¥\8bà¤\95नà¥\8dफरà¥\8dम à¤ªà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¥\8bà¤\97 à¤¸à¤\82पादित à¤\95 à¤¸à¤\95त à¤¬à¤¾à¥¤\nनà¥\80à¤\9aà¥\87 à¤ªà¥\8dरसà¤\82à¤\97 à¤¬à¤¤à¤¾à¤µà¥\87 à¤\96ातिर à¤¸à¤­à¤¸à¥\87 à¤¨à¤¯à¤¾ à¤²à¥\89à¤\97 à¤\8fà¤\82à¤\9fà¥\8dरà¥\80 दिहल जात बा:",
        "cascadeprotectedwarning": "<strong>चेतावनी:</strong> ई पन्ना सुरक्षित बा जवना से कि खाली [[Special:ListGroupRights|बिसेस अधिकार]] वाला प्रयोगकर्ता लोग संपादन क सकेला काहें से की ई नीचे दिहल गइल छतनार-सुरक्षा वाला {{PLURAL:$1|पन्ना|पन्ना सभ}} में ट्रांसक्लूड हो रहल बाटे:",
        "titleprotectedwarning": "<strong>चेतावनी: ई पन्ना सुरक्षित कइल गइल बा की एकरा के बनावे खातिर [[Special:ListGroupRights|विशेष अधिकार]] होखल जरूरी बा।</strong>\nसंदर्भ खातिर नीचे सबसे नया लॉग एंट्री दिहल जात बा:",
        "templatesused": "ए पन्ना पर इस्तेमाल {{PLURAL:$1|टेम्पलेट|टेम्पलेट कुल}}:",
        "expansion-depth-exceeded-warning": "पन्ना अधिकतम बिस्तार गहिराई के पार क गइल",
        "parser-unstrip-loop-warning": "अनस्ट्रिप लूप पकड़ में आइल बा",
        "unstrip-depth-warning": "अनस्ट्रिप रिकर्शन सीमा पार हो गइल ($1)",
+       "unstrip-depth-category": "पन्ना जहाँ अनस्ट्रिप गहिराई सीमा पार क चुकल बाटे",
+       "unstrip-size-warning": "अनस्ट्रिप साइज के सीमा पार हो चुकल बा ($1)",
+       "unstrip-size-category": "पन्ना जहाँ अनस्ट्रिप साइज के सीमा पार हो चुकल बाटे",
        "converter-manual-rule-error": "मैनुअल भाषा परिवर्तन नियम में खराबी पकड़ल गइल",
        "undo-success": "संपादन वापस कइल जा सकत बा।\nनीचे दिहल तुलना के चेक करीं आ पुष्टी करीं की आप इहे कइल चाहत बाड़ीं, ओकरा बाद बदलाव सहेज के संपादन वापसी के पूरा करीं।",
        "undo-failure": "बीच में अउरी संपादन होखला की कारण ई संपादन वापस नइखे लिहल जा सकत।",
        "revdelete-modify-missing": "आइटम ID $1 के बदलाव करे में खराबी: ई डेटाबेस से गायब बा!",
        "revdelete-no-change": "<strong>चेतावनी:</strong> तारीख $2 के $1 बजे के ई आइटम पहिलहीं से ओही देखावे के सेटिंग वाला बाटे जवन माँगल जाता।",
        "revdelete-concurrent-change": "$1 $2 बजे के आइटम के बदले में खराबी आ रहल बा: बुझाता कि आप के कोसिस करे दौरान एकरा स्थिति के दूसर केहू बदल चुकल बा।\nलॉग चेक करीं।",
+       "revdelete-only-restricted": "तारीख $2, $1 के आइटम के लुकवावे में खराबी आवत बा: आप प्रबंधक लोग के नजर में आवे से आइटम के ना दबा सकत बाड़ीं जबले कि कौनों अउरी विजिबिलिटी बिकल्प के भी न सेलेक्ट करीं।",
+       "revdelete-reason-dropdown": "*हटावे के आम कारण\n** कॉपीराइट उलंघन\n** अनुचित कमेंट भा पर्सनल जानकारी\n** अनुचित प्रयोगकर्तानाँव\n** मानहानि-कारक जानकारी",
        "revdelete-otherreason": "अन्य/अतिरिक्त कारण:",
        "revdelete-reasonotherlist": "अन्य कारण",
        "revdelete-edit-reasonlist": "हटावे के कारण बदलीं",
        "nextn-title": "अगिला $1 {{PLURAL:$1|परिणाम}}",
        "shown-title": "प्रति पन्ना $1 {{PLURAL:$1|परिणाम}} देखाईं",
        "viewprevnext": "देखीं ($1 {{int:pipe-separator}} $2) ($3)",
-       "searchmenu-exists": "'''इ विकि पर ''[[:$1]]'' नाम से एगो पन्ना उपलब्ध बा'''",
+       "searchmenu-exists": "<strong>एह विकि पर \"[[:$1]]\" नाँव से एगो पन्ना मौजूद बा।</strong> {{PLURAL:$2|0=|खोज में मिलल अउरियो रिजल्ट सभ देखीं}}",
        "searchmenu-new": "<strong> ए विकि पर \"[[:$1]]\" नाँव के पन्ना बनाईं !</strong> {{PLURAL:$2|0=|अपनी खोज से मिलल पन्ना भी देखीं|खोज के परिणाम भी देखीं।}}",
        "searchprofile-articles": "सामग्री पन्ना",
        "searchprofile-images": "मल्टीमीडिया",
        "licenses-edit": "लाइसेंस बिकल्प संपादन",
        "license-nopreview": "(नमूना देखल उपलब्ध नइखे)",
        "imgfile": "फाइल",
-       "listfiles": "फाà¤\87ल à¤¸à¥\82à¤\9aà¥\80",
+       "listfiles": "फाà¤\87ल à¤²à¤¿à¤¸à¥\8dà¤\9f",
        "listfiles_thumb": "चिप्पी",
        "listfiles_date": "तिथि",
        "listfiles_name": "नाँव",
        "filehist-thumbtext": "$1 ले के संस्करण के चिप्पी रूप।",
        "filehist-nothumb": "बिन थम्बनेल",
        "filehist-user": "प्रयोगकर्ता",
-       "filehist-dimensions": "à¤\86याम",
+       "filehist-dimensions": "डाà¤\87मà¥\87à¤\82शन",
        "filehist-filesize": "फाईल के आकार",
        "filehist-comment": "टिप्पणी",
        "imagelinks": "फाइल के उपयोग",
        "sharedupload": "इ फाईल $1 से बा आ दुसर परियोजना में प्रयोग करल जा सकत बा।",
        "sharedupload-desc-there": "इ फाईल $1 से बा आ दुसर परियोजना में प्रयोग करल जा सकत बा। अधिक जानकारी खातिर कृपया [$2 फाईल विवरण पन्ना] देखीं।",
        "sharedupload-desc-here": "ई फाइल $1 से बा आ अउरी प्रोजेक्ट भी एकर इस्तेमाल कर सकत बाड़ें। \nएकर विवरण [$2 फाइल विवरण पन्ना] नीचे देखावल गइल बा।",
-       "filepage-nofile": "à¤\87 à¤¨à¤¾à¤® à¤¸à¥\87 à¤\95à¥\8cनà¥\8b à¤«à¤¾à¤\88ल à¤\89पलबà¥\8dध à¤¨à¤\88खे।",
+       "filepage-nofile": "à¤\8fह à¤¨à¤¾à¤® à¤¸à¥\87 à¤\95à¥\8cनà¥\8b à¤«à¤¾à¤\87ल à¤®à¥\8cà¤\9cà¥\82द à¤¨à¤\87खे।",
        "filepage-nofile-link": "इ नाम से कौनो फाईल उपलब्ध नईखे, लेकिन रउआ [$1 के अपलोड कर] सकत बानी।",
        "uploadnewversion-linktext": "इ फाईल के नया संस्करण लादीं।",
        "shared-repo-from": "$1 से",
        "protectedtitles": "सुरक्षित शीर्षक",
        "protectedtitlesempty": "कौनों टाइटिल के सुरक्षा एह पैमान पर नइखे।",
        "protectedtitles-submit": "शीर्षक देखीं",
-       "listusers": "सदसà¥\8dयसà¥\82à¤\9aà¥\80",
+       "listusers": "पà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤²à¤¿à¤¸à¥\8dà¤\9f",
        "listusers-editsonly": "उहे सदस्य देखाईं जे संपादन कइले होखे",
        "listusers-creationsort": "बनवले की तारीख की हिसाब से सरियाईं",
        "listusers-desc": "घटत क्रम से सरियाईं",
        "trackingcategories": "नजर रखे वाला श्रेणीसभ",
        "trackingcategories-msg": "निगरानी श्रेणी",
        "trackingcategories-name": "संदेस नाँव",
-       "emailuser": "à¤\88 प्रयोगकर्ता के ईमेल करीं",
+       "emailuser": "à¤\8fह प्रयोगकर्ता के ईमेल करीं",
        "emailusername": "प्रयोगकर्तानाँव:",
        "emailfrom": "भेजे वाला:",
        "emailto": "पावे वाला:",
        "mycontris": "योगदान",
        "anoncontribs": "योगदान",
        "contribsub2": "{{GENDER:$3|$1}} ($2) खातिर",
-       "nocontribs": "à¤\88 à¤®à¤¾à¤¨à¤¦à¤\82ड à¤¸à¥\87 à¤®à¤¿à¤²à¤¤ à¤\9cà¥\81लत कौनो बदलाव ना मिलल।",
+       "nocontribs": "à¤\8fह à¤ªà¥\88माना à¤¸à¥\87 à¤®à¥\88à¤\9a à¤\95रत कौनो बदलाव ना मिलल।",
        "uctop": "(वर्तमान)",
        "month": "महीना से (आ ओ से पहिले):",
        "year": "साल से (आ ओ से पहिले):",
        "whatlinkshere-title": "पन्ना जेवन \"$1\" से जुड़ल बा",
        "whatlinkshere-page": "पन्ना:",
        "linkshere": "<strong>[[:$1]]</strong> से नीचे दिहल पन्ना जुड़ल बाने:",
-       "nolinkshere": "'''[[:$1]]''' à¤¸à¥\87 à¤\95à¥\8cनà¥\8b à¤ªà¤¨à¥\8dना à¤¨à¤\88खे जुड़ल।",
+       "nolinkshere": "'''[[:$1]]''' à¤¸à¥\87 à¤\95à¥\8cनà¥\8b à¤ªà¤¨à¥\8dना à¤¨à¤\87खे जुड़ल।",
        "nolinkshere-ns": "चुनल गईल सन्दर्भ में '''[[:$1]]''' से कौनो पन्ना ना जुड़ेला।",
        "isredirect": "अनुप्रेषित पन्ना",
        "istemplate": "ट्रांस्क्लूजन",
        "whatlinkshere-hideimages": "$1 फाइल कड़ी",
        "whatlinkshere-filters": "छननी",
        "blockip": "{{GENDER:$1|सदस्य}} अवरोधित करीं",
-       "ipboptions": "२ घंटे:2 hours,१ दिन:1 day,३ दिन:3 days,१ हफ्ता:1 week,२ हफ्ते:2 weeks,१ महिना:1 month,३ महिने:3 months,६ महिने:6 months,१ साल:1 year,हमेशा खातिर:infinite",
+       "ipboptions": "2 घंटा:2 hours,1 दिन:1 day,3 दिन:3 days,1 हप्ता:1 week,2 हप्ता:2 weeks,1 महीना:1 month,3 महीना:3 months,6 महीना:6 months,1 साल:1 year,अनिश्चित समय खातिर:infinite",
        "blocklist": "अवरोधित प्रयोगकर्तासभ",
        "infiniteblock": "अनिश्चितकाल",
        "blocklink": "रोक लगाईं",
        "unblocklink": "ताला खोलीं",
        "change-blocklink": "ब्लॉक बदलीं",
        "contribslink": "योगदान",
-       "blocklogpage": "निषà¥\8dà¤\95à¥\8dरिय à¤\96ाता",
+       "blocklogpage": "रà¥\8bà¤\95 à¤²à¥\89à¤\97",
        "blocklogentry": "[[$1]] के ब्लॉक कइल गइल, समाप्ती के अवधि $2 $3",
        "reblock-logentry": "[[$1]] खातिर रोक सेटिंग बदलल गइल आ अब समाप्ती समय बा $2 $3",
        "block-log-flags-nocreate": "खाता निर्माण सक्षम नइखे",
        "tooltip-ca-addsection": "एगो नया खंड शुरु करीं",
        "tooltip-ca-viewsource": "ई पन्ना सुरक्षित कइल गइल बा। आप एकर स्रोत देख सकत बानी।",
        "tooltip-ca-history": "ए पन्ना के पछिला संशोधन",
-       "tooltip-ca-protect": "à¤\87 à¤ªà¤¨à¥\8dना à¤\95à¥\87 à¤¸à¤\82रà¤\95à¥\8dषित à¤\95रà¥\80à¤\82।",
+       "tooltip-ca-protect": "à¤\88 à¤ªà¤¨à¥\8dना à¤¸à¥\81रà¤\95à¥\8dषित à¤\95रà¥\80à¤\82",
        "tooltip-ca-unprotect": "ई पन्ना के सुरक्षा बदलीं।",
        "tooltip-ca-delete": "ई पन्ना मिटाईं",
        "tooltip-ca-move": "एह पन्ना के स्थानांतरण करीं",
index 313dca9..736af83 100644 (file)
        "createacct-emailoptional": "ইমেইল ঠিকানা (ঐচ্ছিক)",
        "createacct-email-ph": "আপনার ইমেইল ঠিকানা যোগ করুন",
        "createacct-another-email-ph": "আপনার ইমেইল ঠিকানা প্রবেশ করান",
-       "createaccountmail": "à¦\8fà¦\95à¦\9fি à¦°â\80\8cà§\8dযানà§\8dডম à¦ªà¦¾à¦¸à¦\93য়ারà§\8dড à¦¨à¦¿à¦°à§\8dবাà¦\9aন করুন এবং নির্ধারিত ইমেইল ঠিকানায় পাঠিয়ে দিন",
+       "createaccountmail": "সাময়িà¦\95ভাবà§\87 à¦\85à¦\9cানা à¦ªà¦¾à¦¸à¦\93য়ারà§\8dড à¦¬à§\8dযবহার করুন এবং নির্ধারিত ইমেইল ঠিকানায় পাঠিয়ে দিন",
        "createaccountmail-help": "পাসওয়ার্ড জানা ছাড়াই অন্য ব্যক্তির জন্য অ্যাকাউন্ট তৈরি করতে ব্যবহার করা যেতে পারে।",
        "createacct-realname": "আসল নাম (ঐচ্ছিক)",
        "createacct-reason": "কারণ",
index 2f2e150..9fcfa2f 100644 (file)
                        "Beyronvan"
                ]
        },
-       "tog-underline": "Ù\84Û\8cÙ\86Ú©Ù\87اÛ\8c Ø®Ø· Ø¨Ù\87 Ø²Û\8cر",
-       "tog-hideminor": "من ته نبیدن تغییرات کوچیک",
+       "tog-underline": "Ù\87Ù\88Ù\85Ù¾Û\8cÚ¤Ù±Ù\86دا Ø²Û\8cر Ø®Ù±ØªØ¯Ø§ر",
+       "tog-hideminor": "دٱم تی نٱبیڌن آلشتا کۊچیر",
        "tog-extendwatchlist": "گپ کردن نوم گه آ مو سی دیئن همه آلشتا نه فقط هونو که بیشتر ز همه انجوم ابون.",
-       "tog-usenewrc": "گپ کردن تغییرات آخری - جاوااسکریپت",
-       "tog-numberheadings": "Ø´Ù\85ارÙ\87 Ù\88Ù\86دÙ\86 Ø®Ù\88دکار Ø³Û\8c Ø³Ø±Ø®Ø· Ù\87ا",
-       "tog-showtoolbar": "نشو دادن تغییرات  تولبار  یا   جای نشودادن ابزارها- جاوااسکریپت",
-       "tog-editondblclick": "اصلاح صفحات با دوبار کلیک - جاوااسکریپت",
-       "tog-editsectiononrightclick": "امکان اصلاح یه قسمت زه راه راست کلیک کردن رو عنوان  اوقسمت- جاوااسکریپت",
-       "tog-watchcreations": "اضاف کردن اوصفحاتی که خوم درست کردم به فهرست نمایشی",
+       "tog-usenewrc": "جٱرغاٛ کاری آلشتا ڤا آلشتکاری بٱلگاٛیلسۊن و سئیل بٱرگسۊن",
+       "tog-numberheadings": "Ø´Ù\88Ù\85اراÙ\9b Ú¤Ù±Ù\86دÙ\86 Ø®Ù\88دٱÙ\86جÙ\88Ù\85 Ø³Û\8c Ø³Ø±Ø¨Ù±Ù\84گاÙ\9bÛ\8cÙ\84",
+       "tog-showtoolbar": "دیاری کردن تۊلبار ڤیرایشت",
+       "tog-editondblclick": "ڤیرایشت بٱلگاٛیل ڤا دو کئرٱت پۊرنیڌن",
+       "tog-editsectiononrightclick": "ڤیرایشت ڤابیڌن ڤا راست پۊرنیڌن ری بٱرجا داسۊن هر جاگٱ",
+       "tog-watchcreations": "اٛزاف کردن او بٱلگاٛیلی کاٛ خوم راست کردوماٛ و او جانیایلی کاٛ خوم لاهامسۊناٛ مئن سئیل بٱرگ خوم",
        "tog-watchdefault": "اضاف کردن اوصفحاتی که خوم اصلاح کردم به فهرست نمایشی",
        "tog-watchmoves": "اضاف کردن صفحاتی که خوم جابجا کردم به فهرست نمایشی",
        "tog-watchdeletion": "اضاف کردن صفحاتی که خوم پاک کردم به فهرست نمایشی خوم",
@@ -30,7 +30,7 @@
        "tog-enotifwatchlistpages": "امیل به مو وقتی که  صفحه ای که منه فهرست نمایش مونه تغییر کرد",
        "tog-enotifusertalkpages": "امیل به مو وقتی که صفحه گفتگوی مو تغییر کرد",
        "tog-enotifminoredits": "امیل به مو سی صفحات ناقص اصلاح شده",
-       "tog-enotifrevealaddr": "نشودادن امیل مو درامیلهای آگاهی-خبری",
+       "tog-enotifrevealaddr": "دیاری کردن تیرنشۊن ٱنجوماناماٛ مو مئن دیارکاری ایمیلی",
        "tog-shownumberswatching": "نشودادن شماره کاربران درحال کار یاتماشا",
        "tog-oldsig": "امضا ایسنی",
        "tog-fancysig": "امضایل ناتموم",
index 4c68d10..96272c6 100644 (file)
        "prefs-dateformat": "Format datuma",
        "prefs-timeoffset": "Vremenska razlika",
        "prefs-advancedediting": "Opće opcije",
+       "prefs-developertools": "Razvojni alati",
        "prefs-editor": "Uređivač",
        "prefs-preview": "Pregled",
        "prefs-advancedrc": "Napredne opcije",
index a8a23a2..f4158ea 100644 (file)
        "customcssprotected": "No teniu permisos per editar la pàgina CSS perquè conté els paràmetres personals d'un altre usuari.",
        "customjsprotected": "No teniu permisos per editar la pàgina JavaScript perquè conté els paràmetres personals d'un altre usuari.",
        "mycustomcssprotected": "No tens permís per editar aquesta pàgina CSS.",
+       "mycustomjsonprotected": "No teniu permisos per editar aquesta pàgina JSON.",
        "mycustomjsprotected": "No tens permís per editar aquesta pàgina JavaScript.",
        "myprivateinfoprotected": "No tens permís per editar la teva informació privada.",
        "mypreferencesprotected": "No tens permís per editar les teves preferències.",
        "deadendpages": "Pàgines atzucac",
        "deadendpagestext": "Aquestes pàgines no tenen enllaços a altres pàgines del projecte {{SITENAME}}.",
        "protectedpages": "Pàgines protegides",
+       "protectedpages-filters": "Filtres:",
        "protectedpages-indef": "Només proteccions indefinides",
        "protectedpages-summary": "Aquesta pàgina llista les pàgines existents que estan protegides actualment. Per a consultar la llista de títols protegits perquè no puguin crear-se'n pàgines, vegeu [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]].",
        "protectedpages-cascade": "Només proteccions en cascada",
        "apisandbox-dynamic-parameters-add-placeholder": "Nom del paràmetre",
        "apisandbox-dynamic-error-exists": "Ja existeix un paràmetre anomenat \"$1\".",
        "apisandbox-deprecated-parameters": "Paràmetres obsolets",
+       "apisandbox-add-multi": "Afegeix",
        "apisandbox-submit-invalid-fields-title": "Alguns camps no són vàlids",
        "apisandbox-submit-invalid-fields-message": "Corregiu els camps marcats i torneu-ho a provar.",
        "apisandbox-results": "Resultats",
        "filedelete-archive-read-only": "El directori d'arxiu «$1» no té permisos d'escriptura per al servidor web.",
        "previousdiff": "← Edició anterior",
        "nextdiff": "Edició següent →",
-       "mediawarning": "'''Advertència''': Aquest fitxer podria contenir codi maliciós.\nSi l'executeu, podeu comprometre la seguretat del vostre sistema.",
+       "mediawarning": "<strong>Advertiment</strong>: aquest fitxer podria contenir codi maliciós.\nSi l’executeu, podeu comprometre la seguretat del vostre sistema.",
        "imagemaxsize": "Límit de mida d'imatges:<br />''(per a pàgines de descripció de fitxers)''",
        "thumbsize": "Mida de la miniatura:",
        "widthheight": "$1 × $2",
        "version-specialpages": "Pàgines especials",
        "version-parserhooks": "Extensions de l'analitzador",
        "version-variables": "Variables",
+       "version-editors": "Editors",
        "version-antispam": "Prevenció spam",
        "version-other": "Altres",
        "version-mediahandlers": "Connectors multimèdia",
index 897d11b..a5d7511 100644 (file)
        "mytalk": "Сан дийцаре агӀо",
        "anontalk": "Дийцаре",
        "navigation": "Навигаци",
-       "and": "&#32;а",
+       "and": ",",
        "faq": "СиХХ",
        "actions": "Дийраш",
        "namespaces": "ЦӀерийн меттигаш",
        "extlink_tip": "Арахьара хьажорг (йиц ма йе хӀотталушерг http://)",
        "headline_sample": "Йозан корта",
        "headline_tip": "Корта 2-гlа локхаллийца",
-       "nowiki_sample": "ЧÑ\83диллийÑ\88а ÐºÑ\85Ñ\83зе Ð±Ð°Ñ\80амÑ\85lоÑ\82Ñ\82онза Ð¹Ð¾Ð·Ð°.",
+       "nowiki_sample": "Ð\9aÑ\85Ñ\83за Ñ\85Ó\80оÑ\82Ñ\82аде Ñ\85ийÑ\86а Ð¾Ñ\8cÑ\88Ñ\83Ñ\88 Ð´Ð¾Ñ\86Ñ\83 Ð¹Ð¾Ð·Ð°",
        "nowiki_tip": "Тергал ца бо вики-бáрамхlоттор",
        "image_sample": "Example.jpg",
        "image_tip": "Чохь йолу файл",
        "rcfilters-clear-all-filters": "Ерриге литтарш цӀанъян",
        "rcfilters-show-new-changes": "ТӀеххьара хийцамаш",
        "rcfilters-search-placeholder": "Литтаран керла хийцамаш лахар",
+       "rcfilters-empty-filter": "Жигара литтарш дац. Дерриге нисдарш гойтуш ю.",
        "rcfilters-filterlist-title": "Литтарш",
        "rcfilters-filterlist-feedbacklink": "Керла (бета) литтарех лаьцна хьайна хеттарг язде",
        "rcfilters-highlightbutton-title": "Билгалде карийнарш",
        "boteditletter": "б",
        "number_of_watching_users_pageview": "[$1 {{PLURAL:$1|тӀехьожу декъашхо|тӀехьожу декъашхой}}]",
        "rc-change-size-new": "Хийцам бин чул тӀехьа болу барам: $1 {{PLURAL:$1|байт}}",
-       "newsectionsummary": "/* $1 */ Ð\9aеÑ\80ла Ñ\85Ñ\8cедаÑ\80",
+       "newsectionsummary": "/* $1 */ Ð\9aеÑ\80ла Ñ\82ема",
        "rc-enhanced-expand": "Гайта мадарра",
        "rc-enhanced-hide": "Ма дарра дерг къайладаккха",
        "rc-old-title": "дуьххьара кхоьллина яра «$1» цӀарца",
        "apisandbox-continue-help": "{{int:apisandbox-continue}} [https://www.mediawiki.org/wiki/API:Query#Continuing_queries чекхдаккха] тӀеххьара дехар; {{int:apisandbox-continue-clear}} чекхдаккхарца йолу параметраш цӀанъян.",
        "apisandbox-multivalue-all-namespaces": "$1 (Ерриге цӀерийн меттигаш)",
        "apisandbox-multivalue-all-values": "$1 (Дерриге маьӀнаш)",
-       "booksources": "Ð\96айнан хьосташ",
+       "booksources": "Ð\96айнийн хьосташ",
        "booksources-search-legend": "Жайнех лаьцна хаам лахар",
        "booksources-search": "Лахар",
        "booksources-text": "ХӀокху агӀонгахь гул бина сайтийн тӀе хьажоргийн могӀам оцу чохь шуна жайнах лаьцна хаам каро мега. И ю интернет-туьканаш а категорийн библиотекийн категорешкахь лахаран система а.",
        "confirm-unwatch-top": "ДӀаяккха хӀара агӀо хьай тергаме могӀанан юкъар?",
        "confirm-rollback-button": "ХӀаъ",
        "confirm-rollback-top": "ХӀокху агӀона нисдарш юхадаха?",
-       "comma-separator": " a,&#32;",
+       "comma-separator": ",&#32;",
        "quotation-marks": "«$1»",
        "imgmultipageprev": "← хьалха йоьду агӀо",
        "imgmultipagenext": "тӀаьхьа йоьгӀу агӀо →",
index 06ef52f..79f2b2b 100644 (file)
        "protect-cascadeon": "ھەنووکە ئەم پەڕە پارێزراوە بۆ ئەوەی کە لە نێو ئەم {{PLURAL:$1|پەڕە کە پاراستنی تاڤگەییی|پەڕانە کە پاراستنی تاڤگەیییان}} بۆ چالاککراوە، ھێنراوە.\nدەتوانی ئاستی پاراستنی ئەم پەڕە بگۆڕی، بەڵام ھیچ کاریگەرییەکی نابێت لە سەر پاراستنی تاڤگەیی",
        "protect-default": "بە ھەموو بەکارھێنەران ڕێگە بدە",
        "protect-fallback": "تەنیا بە بەکارھێنەران بە مافی «$1» ڕێگە بدە",
-       "protect-level-autoconfirmed": "تەنیا بە [[ویکیپیدیا:بەکارھێنەرانی پەسندکراوی خۆگەڕ|بەکارھێنەرانی پەسندکراوی خۆگەڕ ]] ڕێگە بدە",
+       "protect-level-autoconfirmed": "تەنیا بە بەکارھێنەرانی پەسندکراوی خۆگەڕ ڕێگە بدە",
        "protect-level-sysop": "تەنیا بە بەڕێوەبەران ڕێگە بدە",
        "protect-summary-cascade": "تاڤگەیی",
        "protect-expiring": "بەسەردەچێ لە ڕێکەوتی $1 (UTC)",
index 1adef2e..598b9a4 100644 (file)
        "recentchangeslinked-feed": "Související změny",
        "recentchangeslinked-toolbox": "Související změny",
        "recentchangeslinked-title": "Související změny pro stránku „$1“",
-       "recentchangeslinked-summary": "Vložením názvu stránky uvidíte změny stránek, které na stránku odkazují nebo na které stránka odkazuje. (Pro stránky zařazené do kategorie vložte Kategorie:Název kategorie.) Vámi [[Special:Watchlist|sledované stránky]] jsou <strong>zvýrazněny</strong>.",
+       "recentchangeslinked-summary": "Vložením názvu stránky uvidíte změny stránek, které na stránku odkazují nebo na které stránka odkazuje. (Pro stránky zařazené do kategorie vložte {{ns:category}}:Název kategorie.) Vámi [[Special:Watchlist|sledované stránky]] jsou <strong>zvýrazněny</strong>.",
        "recentchangeslinked-page": "Název stránky:",
        "recentchangeslinked-to": "Zobrazit změny na stránkách odkazujících na zadanou stránku",
        "recentchanges-page-added-to-category": "Stránka [[:$1]] zařazena do kategorie",
        "uploadstash-file-not-found-no-object": "Nepodařilo se vytvořit objekt lokálního souboru pro náhled.",
        "uploadstash-file-not-found-no-remote-thumb": "Načtení náhledu se nepodařilo: $1\nURL = $2",
        "uploadstash-file-not-found-missing-content-type": "Chybí hlavička content-type.",
+       "uploadstash-file-not-found-not-exists": "Nelze najít cestu nebo nejde o soubor.",
        "uploadstash-file-too-large": "Nelze poskytnout soubor větší než $1 bajtů.",
        "uploadstash-not-logged-in": "Není přihlášen žádný uživatel, soubory musí patřit uživatelům.",
        "uploadstash-wrong-owner": "Tento soubor ($1) nepatří aktuálnímu uživateli.",
        "change-blocklink": "změnit blok",
        "contribslink": "příspěvky",
        "emaillink": "poslat e-mail",
-       "autoblocker": "Automatické zablokování kvůli tomu, že vaši IP adresu nedávno {{GENDER:$1|používal uživatel|používala uživatelka}} „[[User:$1|$1]]“.\nDůvod zablokování {{GENDER:$1|uživatele $1|uživatelky $1}}: „$2“",
+       "autoblocker": "Automatické zablokování kvůli tomu, že vaši IP adresu nedávno používal uživatel „[[User:$1|$1]]“.\nDůvod zablokování uživatele $1: „$2“",
        "blocklogpage": "Kniha zablokování",
        "blocklog-showlog": "{{GENDER:$1|Tento uživatel byl dříve blokován.|Tato uživatelka byla dříve blokována.|Tento uživatel byl dříve blokován.}}\nZde je pro přehled zobrazen výpis z knihy zablokování:",
        "blocklog-showsuppresslog": "{{GENDER:$1|Tento uživatel byl zablokován a skryt|Tato uživatelka byla zablokována a skryta}}. Zde je pro přehled zobrazen výpis záznamu utajení:",
        "group-bot.css": "/* Zde uvedené CSS bude ovlivňovat pouze roboty */",
        "group-sysop.css": "/* Zde uvedené CSS bude ovlivňovat pouze správce */",
        "group-bureaucrat.css": "/* Zde uvedené CSS bude ovlivňovat pouze byrokraty */",
+       "common.json": "/* Zde uvedený JSON se načte pro všechny uživatele při načtení každé stránky. */",
        "common.js": "/* Zde uvedený JavaScript bude použit pro všechny uživatele při načtení každé stránky. */",
        "group-autoconfirmed.js": "/* Zde uvedený JavaScript bude použit pouze pro automaticky schválené uživatele */",
        "group-user.js": "/* Zde uvedený JavaScript bude použit pouze pro registrované uživatele */",
index cd66ff6..3382b30 100644 (file)
        "tog-hidepatrolled": "Kontrollierte Änderungen in den „Letzten Änderungen“ ausblenden",
        "tog-newpageshidepatrolled": "Kontrollierte Seiten bei den „Neuen Seiten“ ausblenden",
        "tog-hidecategorization": "Kategorisierungen von Seiten ausblenden",
-       "tog-extendwatchlist": "Alle und nicht nur die aktuellsten Änderungen in der Beobachtungsliste anzeigen",
+       "tog-extendwatchlist": "Alle Änderungen in der Beobachtungsliste anzeigen, nicht nur die aktuellsten",
        "tog-usenewrc": "Änderungen auf „Letzte Änderungen“ und der Beobachtungsliste nach Seite gruppieren",
        "tog-numberheadings": "Überschriften automatisch nummerieren",
        "tog-showtoolbar": "Bearbeiten-Werkzeugleiste anzeigen",
        "recentchangeslinked-feed": "Änderungen an verlinkten Seiten",
        "recentchangeslinked-toolbox": "Änderungen an verlinkten Seiten",
        "recentchangeslinked-title": "Änderungen an Seiten, die von „$1“ verlinkt sind",
-       "recentchangeslinked-summary": "Gib einen Seitennamen ein, um Änderungen auf Seiten zu sehen, die auf oder von dieser Seite verlinkt sind. Um Mitglieder einer Kategorie zu sehen, gib „Kategorie:''Name der Kategorie''“ ein. Änderungen an Seiten auf [[Special:Watchlist|deiner Beobachtungsliste]] sind <strong>fett</strong> hervorgehoben.",
+       "recentchangeslinked-summary": "Gib einen Seitennamen ein, um Änderungen auf Seiten zu sehen, die auf oder von dieser Seite verlinkt sind. Um Mitglieder einer Kategorie zu sehen, gib „{{ns:category}}:''Name der Kategorie''“ ein. Änderungen an Seiten auf [[Special:Watchlist|deiner Beobachtungsliste]] sind <strong>fett</strong> hervorgehoben.",
        "recentchangeslinked-page": "Seite:",
        "recentchangeslinked-to": "Zeige nur Änderungen an Seiten, die auf diese Seite verlinken",
        "recentchanges-page-added-to-category": "[[:$1]] zur Kategorie hinzugefügt",
index 489a8e3..2ad2c5a 100644 (file)
        "apisandbox": "API qumdor",
        "apisandbox-submit": "Bıwazê",
        "apisandbox-reset": "Bestere",
-       "apisandbox-retry": "Fına",
+       "apisandbox-retry": "Anciya bıcerrebne",
        "apisandbox-examples": "Misali",
        "apisandbox-dynamic-parameters": "Parametreya debyayi",
        "apisandbox-dynamic-parameters-add-label": "Parametre dek:",
index fcdaba0..2622727 100644 (file)
        "rcfilters-watchlist-showupdated": "Σελίδες που έχουν υποστεί αλλαγές από την τελευταία φορά που τις επισκεφθήκατε εμφανίζονται με '''έντονους χαρακτήρες'''.",
        "rcfilters-preference-label": "Απόκρυψη της βελτιωμένης έκδοσης των Πρόσφατων Αλλαγών",
        "rcfilters-preference-help": "Αναστέλλει τον επανασχεδιασμό διεπαφής 2017 και όλα τα εργαλεία που προστέθηκαν στη συνέχεια και από τότε.",
+       "rcfilters-filter-showlinkedfrom-option-label": "<strong>Σελίδες που συνδέονται από</strong> τη επιλεγμένη σελίδα",
+       "rcfilters-filter-showlinkedto-label": "Εμφάνιση αλλαγών σε σελίδες που συνδέουν σε",
+       "rcfilters-target-page-placeholder": "Εισαγάγετε όνομα σελίδας (ή κατηγορίας)",
        "rcnotefrom": "Παρακάτω {{PLURAL:$5|είναι η αλλαγή|είναι οι αλλαγές}} από <strong>$3, $4</strong> (έως <strong>$1</strong> που εμφανίζεται).",
+       "rclistfromreset": "Επαναφορά ρύθμισης ημερομηνίας",
        "rclistfrom": "Εμφάνιση νέων αλλαγών αρχίζοντας από τις $3 στις $2",
        "rcshowhideminor": "$1 μικροεπεξεργασιών",
        "rcshowhideminor-show": "Εμφάνιση",
        "recentchangeslinked-feed": "Σχετικές αλλαγές",
        "recentchangeslinked-toolbox": "Σχετικές αλλαγές",
        "recentchangeslinked-title": "Αλλαγές σχετικές με το «$1»",
-       "recentchangeslinked-summary": "Εισαγάγετε ένα όνομα σελίδας για να δείτε τις αλλαγές σε σελίδες που συνδέονται ή από αυτή τη σελίδα. (Για να δείτε τα μέλη μιας κατηγορίας, εισαγάγετε Κατηγορία:Όνομα κατηγορίας.)\nΑλλαγές σε σελίδες στην [[Special:Watchlist|λίστα παρακολούθησής]] σας είναι <strong>έντονες</strong>.",
+       "recentchangeslinked-summary": "Εισαγάγετε ένα όνομα σελίδας για να δείτε τις αλλαγές σε σελίδες που συνδέονται ή από αυτή τη σελίδα. (Για να δείτε τα μέλη μιας κατηγορίας, εισαγάγετε {{ns:category}}:Όνομα κατηγορίας.)\nΑλλαγές σε σελίδες στην [[Special:Watchlist|λίστα παρακολούθησής]] σας είναι <strong>έντονες</strong>.",
        "recentchangeslinked-page": "Όνομα σελίδας:",
        "recentchangeslinked-to": "Εμφάνιση αλλαγών σε σελίδες συνδεδεμένες με την δεδομένη σελίδα αντί αυτής",
        "recentchanges-page-added-to-category": "Η σελίδα [[:$1]] προστέθηκε στην κατηγορία",
        "pageswithprop-legend": "Σελίδες με ιδιότητα σελίδας",
        "pageswithprop-text": "Αυτή η σελίδα ταξινομεί σελίδες που χρησιμοποιούν μια συγκεκριμένη ιδιότητα σελίδας.",
        "pageswithprop-prop": "Όνομα ιδιότητας:",
+       "pageswithprop-reverse": "Ταξινόμηση σε αντίστροφη σειρά",
+       "pageswithprop-sortbyvalue": "Ταξινόμηση ανά τιμή ιδιότητας",
        "pageswithprop-submit": "Μετάβαση",
        "pageswithprop-prophidden-long": "τιμή ιδιότητας μακρού κειμένου κρυμμένη ($1)",
        "pageswithprop-prophidden-binary": "τιμή ιδιότητας δυαδικών δεδομένων κρυμμένη ($1)",
        "log-action-filter-contentmodel-new": "Δημιουργία σελίδας με μη προεπιλεγμένο μοντέλο περιεχομένου",
        "log-action-filter-delete-delete": "Διαγραφή σελίδας",
        "log-action-filter-delete-restore": "Ξεδιαγραφή σελίδας",
+       "log-action-filter-delete-event": "Διαγραφή μητρώου",
+       "log-action-filter-delete-revision": "Διαγραφή αναθεώρησης",
        "log-action-filter-import-interwiki": "Εισαγωγή Transwiki",
        "log-action-filter-import-upload": "Εισαγωγή μέσω ανεβάσματος XML",
        "log-action-filter-managetags-create": "Δημιουργία ετικέτας",
        "log-action-filter-managetags-delete": "Διαγραφή ετικέττας",
+       "log-action-filter-managetags-activate": "Ενεργοποίηση ετικέτας",
+       "log-action-filter-managetags-deactivate": "Απενεργοποίηση ετικέτας",
+       "log-action-filter-newusers-create": "Δημιουργία από ανώνυμο χρήστη",
+       "log-action-filter-newusers-create2": "Δημιουργία από εγγεγραμμένο χρήστη",
        "log-action-filter-newusers-autocreate": "Αυτόματη δημιουργία",
        "log-action-filter-patrol-patrol": "Χειροκίνητη περιπολία",
        "log-action-filter-patrol-autopatrol": "Αυτόματη περιπολία",
        "log-action-filter-protect-move_prot": "Μετακίνηση προστασίας",
        "log-action-filter-rights-rights": "Χειροκίνητη αλλαγή",
        "log-action-filter-rights-autopromote": "Αυτόματη αλλαγή",
+       "log-action-filter-suppress-event": "Καταστολή μητρώου",
+       "log-action-filter-suppress-revision": "Καταστολή αναθεώρησης",
+       "log-action-filter-suppress-delete": "Καταστολή σελίδας",
        "log-action-filter-upload-upload": "Νέα μεταφόρτωση",
        "log-action-filter-upload-overwrite": "Επαναμεταφόρτωση",
        "authmanager-create-disabled": "Η δημιουργία λογαριασμού έχει απενεργοποιηθεί.",
        "changecredentials-submit": "Αλλαγή πιστοποιητικών",
        "removecredentials": "Αφαίρεση πιστοποιητικών",
        "removecredentials-submit": "Αφαίρεση πιστοποιητικών",
+       "credentialsform-provider": "Τύπος πιστοποιητικών:",
        "credentialsform-account": "Όνομα λογαριασμού:",
        "cannotlink-no-provider-title": "Δεν υπάρχουν συνδέσιμοι λογαριασμοί",
        "cannotlink-no-provider": "Δεν υπάρχουν συνδέσιμοι λογαριασμοί.",
index 741738a..4befdc5 100644 (file)
        "botpasswords-existing": "Existing bot passwords",
        "botpasswords-createnew": "Create a new bot password",
        "botpasswords-editexisting": "Edit an existing bot password",
+       "botpasswords-label-needsreset": "(password needs reset)",
        "botpasswords-label-appid": "Bot name:",
        "botpasswords-label-create": "Create",
        "botpasswords-label-update": "Update",
        "botpasswords-restriction-failed": "Bot password restrictions prevent this login.",
        "botpasswords-invalid-name": "The username specified does not contain the bot password separator (\"$1\").",
        "botpasswords-not-exist": "User \"$1\" does not have a bot password named \"$2\".",
+       "botpasswords-needs-reset": "The bot password for bot name \"$2\" of {{GENDER:$1|user}} \"$1\" must be reset.",
        "resetpass_forbidden": "Passwords cannot be changed",
        "resetpass_forbidden-reason": "Passwords cannot be changed: $1",
        "resetpass-no-info": "You must be logged in to access this page directly.",
        "prefs-watchlist-edits": "Maximum number of changes to show in watchlist:",
        "prefs-watchlist-edits-max": "Maximum number: 1000",
        "prefs-watchlist-token": "Watchlist token:",
+       "prefs-watchlist-managetokens": "Manage tokens",
        "prefs-misc": "Misc",
        "prefs-resetpass": "Change password",
        "prefs-changeemail": "Change or remove email address",
        "recentchangescount": "Number of edits to show in recent changes, page histories, and in logs, by default:",
        "prefs-help-recentchangescount": "Maximum number: 1000",
        "prefs-help-watchlist-token2": "This is the secret key to the web feed of your watchlist.\nAnyone who knows it will be able to read your watchlist, so do not share it.\nIf you need to, [[Special:ResetTokens|you can reset it]].",
+       "prefs-help-tokenmanagement": "You can see and reset the secret key for your account that can access the Web feed of your watchlist. Anyone who knows the key will be able to read your watchlist, so do not share it.",
        "savedprefs": "Your preferences have been saved.",
        "savedrights": "The user groups of {{GENDER:$1|$1}} have been saved.",
        "timezonelegend": "Time zone:",
        "recentchangeslinked-feed": "Related changes",
        "recentchangeslinked-toolbox": "Related changes",
        "recentchangeslinked-title": "Changes related to \"$1\"",
-       "recentchangeslinked-summary": "Enter a page name to see changes on pages linked to or from that page. (To see members of a category, enter Category:Name of category). Changes to pages on [[Special:Watchlist|your Watchlist]] are in <strong>bold</strong>.",
+       "recentchangeslinked-summary": "Enter a page name to see changes on pages linked to or from that page. (To see members of a category, enter {{ns:category}}:Name of category). Changes to pages on [[Special:Watchlist|your Watchlist]] are in <strong>bold</strong>.",
        "recentchangeslinked-page": "Page name:",
        "recentchangeslinked-to": "Show changes to pages linked to the given page instead",
        "recentchanges-page-added-to-category": "[[:$1]] added to category",
index 9d4b014..7353f90 100644 (file)
        "autoredircomment": "Alidirektigis al [[$1]]",
        "autosumm-new": "Nova paĝo kun '$1'",
        "autosumm-newblank": "Kreis nulan paĝon",
+       "size-bytes": "$1 {{PLURAL:$1|bajto|bajtoj}}",
        "lag-warn-normal": "Ŝanĝoj pli novaj ol $1 {{PLURAL:$1|sekundo|sekundoj}} eble ne estos montrataj en ĉi tiu listo.",
        "lag-warn-high": "Pro malrapideco de la servila datumbazo, ŝanĝoj pli novaj ol $1 {{PLURAL:$1|sekundo|sekundoj}} eble ne montriĝos en ĉi tiu listo.",
        "watchlistedit-normal-title": "Redakti atentaron",
index a69aa69..7199df9 100644 (file)
        "changepassword-throttled": "Has intentado acceder demasiadas veces recientemente.\nEspera $1 antes de intentarlo de nuevo.",
        "botpasswords": "Contraseñas de bots",
        "botpasswords-summary": "Las <em>contraseñas de bots</em> permiten el acceso a una cuenta de usuario mediante la API sin usar las credenciales principales de la cuenta. Los derechos de un usuario mientras haya iniciado sesión con una contraseña de bot pueden estar restringidos.\n\nSi no sabes por qué querrías hacer esto, probablemente no deberías hacerlo. Nadie debería pedirte que generes una de estas claves y que se la entregues.",
-       "botpasswords-disabled": "Las contraseñas de bot están desactivadas.",
+       "botpasswords-disabled": "Las contraseñas de robot están desactivadas.",
        "botpasswords-no-central-id": "Para usar una contraseña de bot, debes estar conectado a una cuenta centralizada.",
        "botpasswords-existing": "Contraseñas de bots existentes",
-       "botpasswords-createnew": "Crear una nueva contraseña de bot",
-       "botpasswords-editexisting": "Editar una contraseña de bot existente",
-       "botpasswords-label-appid": "Nombre del bot:",
+       "botpasswords-createnew": "Crear una contraseña de robot nueva",
+       "botpasswords-editexisting": "Editar una contraseña de robot existente",
+       "botpasswords-label-appid": "Nombre del robot:",
        "botpasswords-label-create": "Crear",
        "botpasswords-label-update": "Actualizar",
        "botpasswords-label-cancel": "Cancelar",
        "botpasswords-label-grants": "Permisos aplicables:",
        "botpasswords-help-grants": "Cada concesión le da acceso a los permisos listados que el usuario ya posea. Habilitar una concesión aquí no proporciona acceso a ningún permiso que tu cuenta de usuario no tendría de otra manera. Véase la [[Special:ListGrants|lista de concesiones]] para más información.",
        "botpasswords-label-grants-column": "Concedido",
-       "botpasswords-bad-appid": "El nombre del bot \"$1\" no es válido.",
+       "botpasswords-bad-appid": "El nombre del robot «$1» no es válido.",
        "botpasswords-insert-failed": "No se pudo agregar el nombre del bot \"$1\". ¿Ya ha sido añadido?",
        "botpasswords-update-failed": "No se pudo actualizar el nombre del bot \"$1\". ¿Ha sido borrado?",
-       "botpasswords-created-title": "Se creó la contraseña de bot",
+       "botpasswords-created-title": "Se creó la contraseña de robot",
        "botpasswords-created-body": "Se creó la contraseña del robot «$1» perteneciente {{GENDER:$2|al usuario|a la usuaria}} «$2».",
-       "botpasswords-updated-title": "Se actualizó la contraseña de bot",
+       "botpasswords-updated-title": "Se actualizó la contraseña de robot",
        "botpasswords-updated-body": "Se actualizó la contraseña del robot «$1» perteneciente {{GENDER:$2|al usuario|a la usuaria}} «$2».",
-       "botpasswords-deleted-title": "Se eliminó la contraseña de bot",
+       "botpasswords-deleted-title": "Se eliminó la contraseña de robot",
        "botpasswords-deleted-body": "Se eliminó la contraseña del robot «$1» perteneciente {{GENDER:$2|al usuario|a la usuaria}} «$2».",
        "botpasswords-newpassword": "La contraseña nueva para acceder con <strong>$1</strong> es <strong>$2</strong>. <em>Guarda esta información para su consulta futura.</em> <br> (En caso de robots antiguos que requieren que el nombre de acceso coincida con el de usuario, también puedes utilizar <strong>$3</strong> como nombre de usuario y <strong>$4</strong> como contraseña.)",
        "botpasswords-no-provider": "BotPasswordsSessionProvider no está disponible.",
        "group": "Grupo:",
        "group-user": "Usuarios",
        "group-autoconfirmed": "Autoconfirmados",
-       "group-bot": "Bots",
+       "group-bot": "Robots",
        "group-sysop": "Administradores",
        "group-bureaucrat": "Burócratas",
        "group-suppress": "Supresores de Flow",
        "group-all": "(todos)",
        "group-user-member": "{{GENDER:$1|usuario|usuaria}}",
        "group-autoconfirmed-member": "{{GENDER:$1|autoconfirmado|autoconfirmada}}",
-       "group-bot-member": "{{GENDER:$1|bot}}",
+       "group-bot-member": "{{GENDER:$1|robot}}",
        "group-sysop-member": "{{GENDER:$1|administrador|administradora}}",
        "group-bureaucrat-member": "{{GENDER:$1|burócrata}}",
        "group-suppress-member": "{{GENDER:$1|supresor|supresora}} de Flow",
        "grouppage-user": "{{ns:project}}:Usuarios",
        "grouppage-autoconfirmed": "{{ns:project}}:Autoconfirmados",
-       "grouppage-bot": "{{ns:project}}:Bots",
+       "grouppage-bot": "{{ns:project}}:Robots",
        "grouppage-sysop": "{{ns:project}}:Administradores",
        "grouppage-bureaucrat": "{{ns:project}}:Burócratas",
        "grouppage-suppress": "{{ns:project}}:Supresores de Flow",
        "right-editmyprivateinfo": "Editar su propia información privada (ej.: correo electrónico, nombre real)",
        "right-editmyoptions": "Editar tus preferencias",
        "right-rollback": "Revertir rápidamente las ediciones del último usuario que modificó una página en particular",
-       "right-markbotedits": "Marcar ediciones revertidas como ediciones de bot",
+       "right-markbotedits": "Marcar las reversiones como ediciones de robot",
        "right-noratelimit": "No resultar afectado por los límites de frecuencia de edición",
        "right-import": "Importar páginas desde otras wikis",
        "right-importupload": "Importar páginas desde un archivo",
        "recentchanges-label-newpage": "Esta edición creó una página",
        "recentchanges-label-minor": "Esta es una edición menor",
        "recentchanges-label-bot": "Esta edición fue realizada por un robot",
-       "recentchanges-label-unpatrolled": "Esta edición aún no ha sido verificada",
+       "recentchanges-label-unpatrolled": "Aún no se ha verificado esta edición",
        "recentchanges-label-plusminus": "El tamaño de la página cambió esta cantidad de bytes",
        "recentchanges-legend-heading": "<strong>Leyenda:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (ver también la [[Special:NewPages|lista de páginas nuevas]])",
        "rcfilters-filter-user-experience-level-newcomer-label": "Recién llegados",
        "rcfilters-filter-user-experience-level-newcomer-description": "Usuarios registrados con menos de diez ediciones o cuatro días de actividad.",
        "rcfilters-filter-user-experience-level-learner-label": "Aprendices",
-       "rcfilters-filter-user-experience-level-learner-description": "Editores registrados cuya experiencia se ubica entre \"Recién Llegados\" y \"Usuarios experimentados\".",
+       "rcfilters-filter-user-experience-level-learner-description": "Editores registrados cuya experiencia se ubica entre «recién llegados» y «usuarios experimentados».",
        "rcfilters-filter-user-experience-level-experienced-label": "Usuarios experimentados",
        "rcfilters-filter-user-experience-level-experienced-description": "Editores registrados con más de 500 ediciones y 30 días de actividad.",
        "rcfilters-filtergroup-automated": "Contribuciones automatizadas",
-       "rcfilters-filter-bots-label": "Bot",
+       "rcfilters-filter-bots-label": "Robot",
        "rcfilters-filter-bots-description": "Ediciones realizadas por herramientas automatizadas.",
-       "rcfilters-filter-humans-label": "Ser humano (no bot)",
+       "rcfilters-filter-humans-label": "Ser humano (no robot)",
        "rcfilters-filter-humans-description": "Ediciones realizadas por editores humanos.",
        "rcfilters-filtergroup-reviewstatus": "Estado de revisión",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "No patrulladas",
        "rcfilters-filter-reviewstatus-manual-label": "Verificado manualmente",
-       "rcfilters-filter-reviewstatus-auto-description": "Ediciones por usuarios avanzadus cuyo trabajo es marcado automáticamente como verificado.",
+       "rcfilters-filter-reviewstatus-auto-description": "Ediciones por usuarios avanzados cuyo trabajo se marca automáticamente como verificado.",
        "rcfilters-filter-reviewstatus-auto-label": "Autoverificado",
        "rcfilters-filtergroup-significance": "Significación",
        "rcfilters-filter-minor-label": "Ediciones menores",
        "rcfilters-filtergroup-lastRevision": "Últimas revisiones",
        "rcfilters-filter-lastrevision-label": "Última revisión",
        "rcfilters-filter-lastrevision-description": "Solo el cambio más reciente a una página.",
-       "rcfilters-filter-previousrevision-label": "No la última revisión",
-       "rcfilters-filter-previousrevision-description": "Todos los cambios que no son la \"última revisión\".",
+       "rcfilters-filter-previousrevision-label": "No la revisión más reciente",
+       "rcfilters-filter-previousrevision-description": "Todos los cambios que no son la «versión actual».",
        "rcfilters-filter-excluded": "Excluido",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>Estado:</strong> $1",
        "rcfilters-exclude-button-off": "Excluir los seleccionados",
        "import-upload-username-prefix": "Prefijo de interwiki:",
        "import-assign-known-users": "Asignar ediciones a usuarios locales cuando el usuario correspondiente exista localmente",
        "import-comment": "Comentario:",
-       "importtext": "Por favor, exporta el archivo desde el wiki de origen usando la [[Special:Export|herramienta de exportación]], guárdalo en tu disco y súbelo aquí.",
+       "importtext": "Exporta el archivo desde el wiki de origen mediante la [[Special:Export|herramienta de exportación]], guárdalo en tu disco y cárgalo aquí.",
        "importstart": "Importando páginas...",
        "import-revision-count": "$1 {{PLURAL:$1|revisión|revisiones}}",
        "importnopages": "No hay páginas que importar.",
        "noscript.css": "/* Los estilos CSS colocados aquí se aplicarán a los usuarios que hayan desactivado el JavaScript en su navegador */",
        "group-autoconfirmed.css": "/* Los estilos CSS colocados aquí se aplicarán para todos los usuarios del grupo Usuarios autoconfirmados */",
        "group-user.css": "/* Los estilos CSS colocados aquí se aplicarán para todos los usuarios registrados */",
-       "group-bot.css": "/* Los estilos CSS colocados aquí se aplicarán para todos los usuarios del grupo Bots */",
+       "group-bot.css": "/* Los estilos CSS colocados aquí se aplicarán para todos los usuarios del grupo Robots */",
        "group-sysop.css": "/* Los estilos CSS colocados aquí se aplicarán para todos los usuarios del grupo Administradores */",
        "group-bureaucrat.css": "/* Los estilos CSS colocados aquí se aplicarán para todos los usuarios del grupo Burócratas */",
        "common.js": "/* Cualquier código JavaScript escrito aquí se cargará para todos los usuarios en cada carga de página */",
        "group-autoconfirmed.js": "/* Cualquier código JavaScript escrito aquí se cargará para todos los usuarios del grupo Usuarios autoconfirmados */",
        "group-user.js": "/* Cualquier código JavaScript escrito aquí se cargará para todos los usuarios registrados */",
-       "group-bot.js": "/* Cualquier código JavaScript escrito aquí se cargará para todos los usuarios del grupo Bots */",
+       "group-bot.js": "/* Cualquier código JavaScript escrito aquí se cargará para todos los usuarios del grupo Robots */",
        "group-sysop.js": "/* Cualquier código JavaScript escrito aquí se cargará para todos los usuarios del grupo Administradores */",
        "group-bureaucrat.js": "/* Cualquier código JavaScript escrito aquí se cargará para todos los usuarios del grupo Burócratas */",
        "anonymous": "{{PLURAL:$1|Usuario anónimo|Usuarios anónimos}} de {{SITENAME}}",
        "filedelete-archive-read-only": "El servidor web no logra escribir en el directorio archivo \"$1\".",
        "previousdiff": "← Edición anterior",
        "nextdiff": "Edición siguiente →",
-       "mediawarning": "<strong>Advertencia:</strong> este archivo puede contener código malicioso.\nEjecutarlo podría comprometer la seguridad de tu equipo.",
+       "mediawarning": "<strong>Atención:</strong> este archivo puede contener código malicioso.\nEjecutarlo podría comprometer la seguridad de tu equipo.",
        "imagemaxsize": "Límite de tamaño de imagen:<br />''(para páginas de descripción de archivo)''",
        "thumbsize": "Tamaño de las vistas en miniatura:",
        "widthheight": "$1 × $2",
index 7bdd358..4ea1f2d 100644 (file)
@@ -68,7 +68,7 @@
        "tog-watchlisthideminor": "Peida pisiparandused jälgimisloendist",
        "tog-watchlisthideliu": "Peida sisselogitud kasutajate muudatused jälgimisloendist",
        "tog-watchlistreloadautomatically": "Laadi jälgimisloend mõne filtri muutmise järel koheselt uuesti (nõutav JavaScript)",
-       "tog-watchlistunwatchlinks": "Lisa jälgimisloendi sissekannete juurde jälgimisest loobumise või jälgimise otselingid (tumblerfunktsiooni jaoks nõutav JavaScript)",
+       "tog-watchlistunwatchlinks": "Lisa muudatuse juurde otselingid ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}), et jälgimisest loobuda või loobumine tagasi võtta (tumblerfunktsiooni jaoks nõutav JavaScript)",
        "tog-watchlisthideanons": "Peida anonüümsete kasutajate muudatused jälgimisloendist",
        "tog-watchlisthidepatrolled": "Peida kontrollitud muudatused jälgimisloendist",
        "tog-watchlisthidecategorization": "Peida lehekülgede kategoriseerimine",
        "password-login-forbidden": "Selle kasutajanime ja parooli kasutamine on keelatud.",
        "mailmypassword": "Lähtesta parool",
        "passwordremindertitle": "{{SITENAME}} – ajutine parool",
-       "passwordremindertext": "Keegi IP-aadressiga $1, tõenäoliselt sa ise, palus, et talle\nsaadetaks {{GRAMMAR:elative|{{SITENAME}}}} uus parool ($4).\nKasutaja \"$2\" ajutiseks paroolis seati \"$3\". Kui soovid tõepoolest\nuut parooli, pead sisse logima ja uue parooli valima.\nAjutine parool aegub {{PLURAL:$5|ühe|$5}} päeva pärast.\n\nKui uut parooli palus keegi teine või sulle meenus vana parool\nja sa ei soovi seda enam muuta, võid seda teadet eirata ning\njätkata senise parooli kasutamist.",
+       "passwordremindertext": "Keegi IP-aadressiga $1 palus, et talle\nsaadetaks {{GRAMMAR:elative|{{SITENAME}}}} uus parool ($4).\nKasutaja \"$2\" ajutiseks paroolis seati \"$3\". Kui soovid tõepoolest\nuut parooli, pead sisse logima ja uue parooli valima.\nAjutine parool aegub {{PLURAL:$5|ühe|$5}} päeva pärast.\n\nKui uut parooli palus keegi teine või sulle meenus vana parool\nja sa ei soovi seda enam muuta, võid seda teadet eirata ning\njätkata senise parooli kasutamist.",
        "noemail": "Kasutaja $1 e-posti aadressi meil kahjuks pole.",
        "noemailcreate": "Pead sisestama korrektse e-posti aadressi",
        "passwordsent": "Uus parool on saadetud kasutaja $1 registreeritud e-postiaadressil.\nPärast parooli saamist logige palun sisse.",
        "longpageerror": "'''Tõrge: Lehekülge ei saa salvestada, sest sinu esitatud {{PLURAL:$1|ühe|$1}} kilobaidi suurune tekst ületab {{PLURAL:$2|ühekilobaidist|$2-kilobaidist}} ülemmäära.'''",
        "readonlywarning": "<strong>Hoiatus: Andmebaas on lukustatud hooldustöödeks, nii et praegu ei saa parandusi salvestada.</strong>\nVõid teksti hilisemaks kasutamiseks alles hoida tekstifailina.\n\nSüsteemiadministraator, kes andmebaasi lukustas, andis järgmise selgituse: $1",
        "protectedpagewarning": "'''Hoiatus: See lehekülg on lukustatud, nii et ainult administraatori õigustega kasutajad saavad seda redigeerida.'''\nAllpool on toodud uusim logisissekanne:",
-       "semiprotectedpagewarning": "'''Märkus:''' See lehekülg on lukustatud, nii et üksnes registreeritud kasutajad saavad seda muuta.\nAllpool on toodud uusim logisissekanne:",
+       "semiprotectedpagewarning": "<strong>Märkus:</strong> See lehekülg on lukustatud, nii et üksnes automaatselt kinnitatud kasutajad saavad seda muuta.\nAllpool on toodud uusim logisissekanne:",
        "cascadeprotectedwarning": "<strong>Hoiatus:</strong> See lehekülg on nii kaitstud, et ainult [[Special:ListGroupRights|teatud õigustega]] kasutajad saavad seda redigeerida, sest lehekülg on osa {{PLURAL:$1|järgmisest|järgmistest}} kaskaadkaitsega {{PLURAL:$1|leheküljest|lehekülgedest}}:",
        "titleprotectedwarning": "'''Hoiatus: See lehekülg on nii lukustatud, et selle loomiseks on tarvis [[Special:ListGroupRights|eriõigusi]].'''\nAllpool on toodud uusim logisissekanne:",
        "templatesused": "Sellel leheküljel on kasutusel {{PLURAL:$1|järgmine mall|järgmised mallid}}:",
        "suppressionlogtext": "Allpool on nimekiri kustutamistest ja blokeeringutest, millega kaasneb administraatorite eest sisu varjamine.\nJõus olevad keelud ja blokeeringud leiad [[Special:BlockList|blokeerimisnimekirja]].",
        "mergehistory": "Lehekülgede ajalugude liitmine",
        "mergehistory-header": "Siin leheküljel saad ühe lehekülje ajaloo redaktsioonid uuema leheküljega liita.\nVeendu, et selle muudatusega jääb lehekülje redigeerimislugu ajaliselt katkematuks.",
-       "mergehistory-box": "Kahe lehekülje redaktsioonide liitmine:",
+       "mergehistory-box": "Kahe lehekülje redaktsioonide liitmine",
        "mergehistory-from": "Alliklehekülg:",
        "mergehistory-into": "Sihtlehekülg:",
        "mergehistory-list": "Liidetav redigeerimise ajalugu",
        "prefs-dateformat": "Kuupäeva vorming",
        "prefs-timeoffset": "Ajavahe",
        "prefs-advancedediting": "Üldsuvandid",
+       "prefs-developertools": "Arendusriistad",
        "prefs-editor": "Toimeti",
        "prefs-preview": "Eelvaade",
        "prefs-advancedrc": "Täpsemad eelistused",
        "rcfilters-filter-humans-label": "Pole robot",
        "rcfilters-filter-humans-description": "Vahetult inimese tehtud muudatused.",
        "rcfilters-filtergroup-reviewstatus": "Ülevaatuse seis",
+       "rcfilters-filter-reviewstatus-unpatrolled-description": "Muudatused, mida pole käsitsi ega automaatselt kontrollituks märgitud.",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "Kontrollimata",
+       "rcfilters-filter-reviewstatus-manual-description": "Käsitsi kontrollituks märgitud muudatused.",
+       "rcfilters-filter-reviewstatus-manual-label": "Käsitsi kontrollitud",
+       "rcfilters-filter-reviewstatus-auto-description": "Muudatused, mille on teinud eriõigusega kasutajad, kelle kaastöö märgitakse automaatselt kontrollituks.",
+       "rcfilters-filter-reviewstatus-auto-label": "Automaatselt kontrollitud",
        "rcfilters-filtergroup-significance": "Olulisus",
        "rcfilters-filter-minor-label": "Pisimuudatused",
        "rcfilters-filter-minor-description": "Muudatused, mille autor märkis pisimuudatuseks.",
        "deadendpages": "Edasipääsuta leheküljed",
        "deadendpagestext": "Järgmised leheküljed ei viita ühelegi teisele {{GRAMMAR:genitive|{{SITENAME}}}} leheküljele.",
        "protectedpages": "Kaitstud leheküljed",
+       "protectedpages-filters": "Filtrid:",
        "protectedpages-indef": "Ainult määramata ajani kaitstud",
        "protectedpages-summary": "Siin on loetletud olemasolevad leheküljed, mis on praegu kaitstud. Loomise eest kaitstud pealkirjade loendi leiad [[{{#special:ProtectedTitles}}|siit]].",
        "protectedpages-cascade": "Ainult kaskaadkaitsega",
        "apisandbox-dynamic-error-exists": "Parameeter nimega \"$1\" on juba olemas.",
        "apisandbox-deprecated-parameters": "Vananenud parameetrid",
        "apisandbox-fetch-token": "Hangi luba automaatselt",
+       "apisandbox-add-multi": "Lisa",
        "apisandbox-submit-invalid-fields-title": "Mõned väljad on vigased",
        "apisandbox-submit-invalid-fields-message": "Palun paranda märgitud väljad ja proovi uuesti.",
        "apisandbox-results": "Tulemused",
        "fix-double-redirects": "Värskenda kõik siia viitavad ümbersuunamislehed uuele pealkirjale",
        "move-leave-redirect": "Jäta maha ümbersuunamisleht",
        "protectedpagemovewarning": "'''Hoiatus:''' See lehekülg on nii lukustatud, et ainult administraatori õigustega kasutajad saavad seda teisaldada.\nAllpool on toodud uusim logisissekanne:",
-       "semiprotectedpagemovewarning": "'''Pane tähele:''' See lehekülg on lukustatud, nii et ainult registreeritud kasutajad saavad seda teisaldada.\nAllpool on toodud uusim logisissekanne:",
+       "semiprotectedpagemovewarning": "<strong>Pane tähele:</strong> See lehekülg on lukustatud, nii et ainult automaatselt kinnitatud kasutajad saavad seda teisaldada.\nAllpool on toodud uusim logisissekanne:",
        "move-over-sharedrepo": "[[:$1]] on olemas jagatud failivaramus. Faili teisaldamisel selle nime alla varjatakse jagatud failivarmus olev samanimeline fail.",
        "file-exists-sharedrepo": "Valitud failinimi on juba kasutusel jagatud failivaramus.\nPalun kasuta mõnda teist nime.",
        "export": "Lehekülgede eksport",
        "version-specialpages": "Erileheküljed",
        "version-parserhooks": "Parserihaagid",
        "version-variables": "Muutujad",
+       "version-editors": "Toimetid",
        "version-antispam": "Rämpsposti tõkestus",
        "version-other": "Muu",
        "version-mediahandlers": "Meediatöötlejad",
index 5c39a17..c792ba4 100644 (file)
        "savechanges": "Aldaketak gorde",
        "publishpage": "Orrialdea argitaratu",
        "publishchanges": "Aldaketak argitaratu",
+       "savechanges-start": "Aldaketak gorde...",
+       "publishpage-start": "Orrialdea argitaratu...",
+       "publishchanges-start": "Aldaketak argitaratu...",
        "preview": "Aurrebista erakutsi",
        "showpreview": "Aurrebista erakutsi",
        "showdiff": "Aldaketak erakutsi",
        "prefs-dateformat": "Data-formatua",
        "prefs-timeoffset": "Denbora ezberdintasuna",
        "prefs-advancedediting": "Genero aukerak",
+       "prefs-developertools": "Garatzaile tresnak",
        "prefs-editor": "Editorea",
        "prefs-preview": "Aurreikusi",
        "prefs-advancedrc": "Aukera aurreratuak",
        "rcfilters-filter-humans-description": "Gizaki editoreek egindako aldaketak.",
        "rcfilters-filtergroup-reviewstatus": "Berrikuspenaren egoera",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "Patruilagabea",
+       "rcfilters-filter-reviewstatus-manual-label": "Eskuz patruilatuak",
        "rcfilters-filtergroup-significance": "Munta",
        "rcfilters-filter-minor-label": "Aldaketa txikiak",
        "rcfilters-filter-minor-description": "Egileak sailkatutako aldaketa txikiak.",
        "deadendpages": "Orrialde itsuak",
        "deadendpagestext": "Jarraian zerrendatutako orrialdeek ez daukate wikiko beste edozein orrialdetarako loturarik.",
        "protectedpages": "Babestutako orrialdeak",
+       "protectedpages-filters": "Iragazkiak:",
        "protectedpages-indef": "Babes mugagabeak bakarrik",
        "protectedpages-summary": "Orrialde honetan unean babestutako orriak zerrendatzen dira. Sorkuntza babesten duten izenen zerrenda lortzeko, ikusi [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]].",
        "protectedpages-cascade": "Kaskada moduko babesak bakarrik",
        "apisandbox-dynamic-error-exists": "$1 parametro izena dagoeneko existitzen da",
        "apisandbox-deprecated-parameters": "Aurretiaz zehaztutako parametroak",
        "apisandbox-fetch-token": "Token-a automatikoki bete",
+       "apisandbox-add-multi": "Gehitu",
        "apisandbox-submit-invalid-fields-title": "Zelai batzuk ez dute balio.",
        "apisandbox-submit-invalid-fields-message": "Mesedez, zuzendu markatutako zelaiak eta saiatu berrio.",
        "apisandbox-results": "Emaitzak",
        "version-specialpages": "Aparteko orrialdeak",
        "version-parserhooks": "Parser estentsioak",
        "version-variables": "Aldagaiak",
+       "version-editors": "Editoreak",
        "version-antispam": "Spam ekiditea",
        "version-other": "Bestelakoak",
        "version-mediahandlers": "Media gordailuak",
index 4d3f927..0e22f6f 100644 (file)
        "customcssprotected": "Sinulla ei ole oikeutta muuttaa tätä CSS-sivua, koska se sisältää toisen käyttäjän henkilökohtaisia asetuksia.",
        "customjsprotected": "Sinulla ei ole oikeutta muuttaa tätä JavaScript-sivua, koska se sisältää toisen käyttäjän henkilökohtaisia asetuksia.",
        "mycustomcssprotected": "Sinulla ei ole oikeutta muokata tätä CSS-sivua.",
+       "mycustomjsonprotected": "Sinulla ei ole oikeutta muokata tätä JSON-sivua.",
        "mycustomjsprotected": "Sinulla ei ole oikeutta muokata tätä JavaScript-sivua.",
        "myprivateinfoprotected": "Sinulla ei ole oikeutta muuttaa omia yksityisiä tietojasi.",
        "mypreferencesprotected": "Sinulla ei ole oikeutta muuttaa omia asetuksiasi.",
        "rcfilters-filter-humans-label": "Ihminen (ei botti)",
        "rcfilters-filter-humans-description": "Ihmisten tekemät muokkaukset.",
        "rcfilters-filtergroup-reviewstatus": "Sivun partioinnin status",
+       "rcfilters-filter-reviewstatus-unpatrolled-description": "Muutoksia ei ole merkitty manuaalisesti tai automaattisesti partioiduksi.",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "Ei ole partioitu",
+       "rcfilters-filter-reviewstatus-manual-description": "Muutokset merkitty partioiduksi manuaalisesti.",
+       "rcfilters-filter-reviewstatus-manual-label": "Manuaalisesti partioitu",
+       "rcfilters-filter-reviewstatus-auto-description": "Muutokset edistyneiltä käyttäjiltä joiden työ on automaattisesti merkitty partioiduksi.",
+       "rcfilters-filter-reviewstatus-auto-label": "Automaattisesti partioitu",
        "rcfilters-filtergroup-significance": "Merkitys",
        "rcfilters-filter-minor-label": "Pienet muutokset",
        "rcfilters-filter-minor-description": "Muokkaukset, jotka on merkitty pieniksi.",
        "rcfilters-filter-watchlistactivity-seen-label": "Nähdyt muutokset",
        "rcfilters-filter-watchlistactivity-seen-description": "Muutokset sivuihin, joilla olet käynyt muutosten jälkeen.",
        "rcfilters-filtergroup-changetype": "Muutoksen tyyppi",
-       "rcfilters-filter-pageedits-label": "Sivun muokkaukset",
+       "rcfilters-filter-pageedits-label": "Sivun muutokset",
        "rcfilters-filter-pageedits-description": "Muokkaukset wikin sisältöön, keskusteluihin, luokkakuvauksiin…",
        "rcfilters-filter-newpages-label": "Sivujen luonnit",
        "rcfilters-filter-newpages-description": "Muokkaukset, joilla on luotu uusia sivuja.",
        "recentchangeslinked-feed": "Linkitettyjen sivujen muutokset",
        "recentchangeslinked-toolbox": "Linkitettyjen sivujen muutokset",
        "recentchangeslinked-title": "Sivulta $1 linkitettyjen sivujen muutokset",
-       "recentchangeslinked-summary": "Kirjoita sivun nimi nähdäksesi muutokset sivuihin jotka on linkitetty tai ovat tältä sivulta. (Nähdäksesi luokan jäsenet, kirjoita Luokka:Luokan nimi). Muutokset sivuihin\n[[Special:Watchlist|tarkkailulistallasi]] on <strong>lihavoitu</strong>.",
+       "recentchangeslinked-summary": "Kirjoita sivun nimi nähdäksesi muutokset sivuihin jotka on linkitetty tai ovat tältä sivulta. (Nähdäksesi luokan jäsenet, kirjoita {{ns:category}}:Luokan nimi). Muutokset sivuihin [[Special:Watchlist|tarkkailulistallasi]] on <strong>lihavoitu</strong>.",
        "recentchangeslinked-page": "Sivun nimi:",
        "recentchangeslinked-to": "Näytä sen sijaan muutokset sivuihin, joista on linkki tähän sivuun",
        "recentchanges-page-added-to-category": "[[:$1]] lisätty luokkaan",
        "lockmanager-fail-closelock": "Tiedoston $1 lukkotiedostoa ei voitu sulkea.",
        "lockmanager-fail-deletelock": "Tiedoston $1 lukkotiedostoa ei voitu poistaa.",
        "lockmanager-fail-acquirelock": "Tiedostopolulle \"$1\" ei voitu luoda suojausta.",
-       "lockmanager-fail-openlock": "Tiedoston $1 lukkotiedostoa ei voitu avata.",
+       "lockmanager-fail-openlock": "Tiedoston $1 lukkotiedostoa ei voitu avata. Varmista että latauskansiosi on määritetty oikein ja verkkopalvelimellasi on oikeudet kirjoittaa tähän kansioon. Katso https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgUploadDirectory saadaksesi lisätietoja.",
        "lockmanager-fail-releaselock": "Tiedoston $1 lukituksen avaaminen epäonnistui.",
        "lockmanager-fail-db-bucket": "Ei voitu yhdistää riittävästi tietokantoja kohdassa $1.",
        "lockmanager-fail-db-release": "Lukitusten vapauttaminen epäonnistui tietokannassa $1.",
index 7956809..f06f2d4 100644 (file)
        "permissionserrorstext": "Vous n'avez pas la permission d'effectuer l'opération demandée pour {{PLURAL:$1|la raison suivante|les raisons suivantes}} :",
        "permissionserrorstext-withaction": "Vous ne pouvez pas $2, pour {{PLURAL:$1|la raison suivante|les raisons suivantes}} :",
        "contentmodelediterror": "Vous ne pouvez pas modifier cette révision car son modèle de contenu est <code>$1</code>, ce qui diffère du modèle de contenu actuel de la page <code>$2</code>.",
-       "recreate-moveddeleted-warn": "<strong>Attention : vous êtes en train de recréer une page qui a été précédemment supprimée.</strong>\n\nAssurez-vous qu'il est pertinent de poursuivre les modifications sur cette page. \nLe journal des suppressions et des déplacements pour cette page est affiché ci-dessous à titre d'information :",
-       "moveddeleted-notice": "Cette page a été supprimée. \nLe journal des suppressions, des protections et des déplacements de la page est affiché ci-dessous pour référence.",
-       "moveddeleted-notice-recent": "Désolé, cette page a été récemment supprimée (dans les dernières 24 heures).\nLes journaux des suppressions, des protections et des renommages pour la page sont fournis ci-dessous pour référence.",
+       "recreate-moveddeleted-warn": "<strong>Attention : vous êtes en train de recréer une page qui a été précédemment supprimée.</strong>\n\nAssurez-vous qu'il est pertinent de poursuivre les modifications sur cette page.\nLes journaux des suppressions et déplacements pour cette page sont fournis ici pour information :",
+       "moveddeleted-notice": "Cette page a été supprimée.\nLes journaux des suppressions, protections et déplacements pour la page sont fournis ci-dessous pour référence.",
+       "moveddeleted-notice-recent": "Désolé, cette page a été récemment supprimée (dans les dernières 24 heures).\nLes journaux des suppressions, protections et déplacements pour la page sont fournis ci-dessous pour référence.",
        "log-fulllog": "Voir le journal complet",
        "edit-hook-aborted": "Échec de la modification par une extension.\nAucune explication n’a été retournée.",
        "edit-gone-missing": "N’a pas pu mettre à jour la page.\nIl semble qu’elle ait été supprimée.",
        "revdelete-hide-text": "Texte de la révision",
        "revdelete-hide-image": "Masquer le contenu du fichier",
        "revdelete-hide-name": "Masquer la cible et les paramètres",
-       "revdelete-hide-comment": "Modifier le résumé",
+       "revdelete-hide-comment": "Résumé de modification",
        "revdelete-hide-user": "Nom d’utilisateur/Adresse IP de l’éditeur",
        "revdelete-hide-restricted": "Supprimer ces données aux administrateurs ainsi qu'aux autres",
        "revdelete-radio-same": "(ne pas changer)",
        "recentchangeslinked-feed": "Suivi des pages liées",
        "recentchangeslinked-toolbox": "Suivi des pages liées",
        "recentchangeslinked-title": "Suivi des pages associées à « $1 »",
-       "recentchangeslinked-summary": "Entrer un nom de page pour voir les modifications faites récemment sur des pages liées depuis ou vers cette page (pour voir les membres d’une catégorie, entrez Catégorie:Nom de catégorie). Les modifications des pages de [[Special:Watchlist|votre liste de suivi]] sont <strong>en gras</strong>.",
+       "recentchangeslinked-summary": "Entrer un nom de page pour voir les modifications faites récemment sur des pages liées vers ou depuis cette page (pour voir les membres d’une catégorie, entrez {{ns:category}}:Nom de catégorie). Les modifications des pages de [[Special:Watchlist|votre liste de suivi]] sont <strong>en gras</strong>.",
        "recentchangeslinked-page": "Nom de la page :",
        "recentchangeslinked-to": "Afficher les modifications des pages qui comportent un lien vers la page donnée plutôt que l'inverse",
        "recentchanges-page-added-to-category": "[[:$1]] ajouté à la catégorie",
        "upload_directory_missing": "Le répertoire d’import de fichier ($1) est introuvable et n’a pas pu être créé par le serveur web.",
        "upload_directory_read_only": "Le serveur web n’a pas accès en écriture au répertoire d’import de fichier ($1).",
        "uploaderror": "Erreur lors de l’import",
-       "upload-recreate-warning": "<strong>Attention : Un fichier portant ce nom a été supprimé ou déplacé.</strong>\n\nLe journal des suppressions et celui des déplacements de cette page sont affichés ici pour informations :",
+       "upload-recreate-warning": "<strong>Attention : Un fichier portant ce nom a été supprimé ou déplacé.</strong>\n\nLes journaux des suppressions et déplacements pour cette page sont fournis ici pour information :",
        "uploadtext": "Utilisez ce formulaire pour téléverser des fichiers sur le serveur.\nPour voir ou rechercher des images précédemment envoyées, consultez la [[Special:FileList|liste des fichiers téléversés]]. Les envois multiples sont également tracés dans le [[Special:Log/upload|journal des téléversements]], et les suppressions dans le [[Special:Log/delete|journal des suppressions]].\n\nPour inclure un fichier dans une page, utilisez un lien ayant l'un des formats suivants :\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:fichier.jpg]]</nowiki></code></strong>, pour afficher le fichier en pleine résolution (dans le cas d’une image) ;\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:fichier.png|200px|thumb|left|texte descriptif]]</nowiki></code></strong> pour utiliser une miniature de 200 pixels de large dans une boîte à gauche avec « texte descriptif » comme description ;\n* <strong><code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:fichier.ogg]]</nowiki></code></strong> pour relier directement le fichier sans l’afficher.",
        "upload-permitted": "{{PLURAL:$2|Format|Formats}} de fichiers {{PLURAL:$2|autorisé|autorisés}} : $1.",
        "upload-preferred": "{{PLURAL:$2|Format|Formats}} de fichiers {{PLURAL:$2|préféré|préférés}} : $1.",
index 0aad8bc..584759b 100644 (file)
        "loginreqlink": "konèkté so kò",
        "newarticletext": "Zòt té ka swiv roun lyen vèr roun paj ki pa ka ègzisté òkò. \nAtò di kréyé sa paj, antré zòt tèks annan bwat ki aprè (zòt pé konsilté [$1 paj d'èd-a] pou plis enfòrmasyon).\nSi zòt pa rivé{{GENDER:|}} isi pa éròr, kliké asou bouton <strong>Routour</strong> di zòt navigatò.",
        "anontalkpagetext": "----\n<em>Zòt asou paj di diskisyon di oun itilizatò anonim ki pa òkò kréyé di kont ou ki pa ka an itilizé</em>.\nPou sa rézon, nou divèt itilizé so adrès IP pou idantifyé li.\nOun adrès IP pé sa partajé pa plizyò itilizatò.\nSi zòt roun itiliza{{GENDER:|ò|ris}} anonim é si zòt ka kontasté ki dé koumantèr ki pa ka konsèrné zòt sa adrèsé à zòt, zòt pé [[Special:CreateAccount|kréyé roun kont]] ou [[Special:UserLogin|konèkté zòt kò]] atò di évité tout konfizyon fitir ké ròt kontribitò anonim.",
-       "noarticletext": "I pa gen pou moman-an pyès tèks asou sa paj.\nZòt pé [[Special:Search/{{PAGENAME}}|lansé oun sasé asou sa tit]] annan ròt paj-ya,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sasé annan opérasyon-yan lyé]\nou [{{fullurl:{{FULLPAGENAME}}|action=edit}} kréyé sa paj]</span>.",
-       "noarticletext-nopermission": "I pa gen pou moman-an pyès tèks asou sa paj.\nZòt pé [[Special:Search/{{PAGENAME}}|fè roun sasé asou sa tit]] andan ròt paj-ya,\nou <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|paj={{FULLPAGENAMEE}}}} sasé andan journal asosyé]</span>, mè zòt pa gen pèrmisyon di kréyé sa paj.",
+       "noarticletext": "I pa gen atchwèlman pyès tèks asou sa paj.\nZòt pouvé [[Special:Search/{{PAGENAME}}|lansé oun sasé asou sa tit]] annan ròt paj-ya,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} sasé annan opérasyon lyé]\noben [{{fullurl:{{FULLPAGENAME}}|action=edit}} kréyé sa paj]</span>.",
+       "noarticletext-nopermission": "I pa gen atchwèlman pyès tèks asou sa paj.\nZòt pouvé [[Special:Search/{{PAGENAME}}|fè roun sasé asou sa tit]] andan ròt paj-ya,\noben <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|paj={{FULLPAGENAMEE}}}} sasé annan journal asosyé]</span>, mè zòt pa gen pèrmisyon di kréyé sa paj.",
        "userpage-userdoesnotexist-view": "Kont itilizatò-a « $1 » pa anréjistré.",
        "clearyourcache": "<strong>Nòt :</strong> aprè zòt anréjistré zòt modifikasyon, zòt divèt forsé roucharjman konplè di kach di zòt navigatò pou wè chanjman-yan.\n* <strong>Firefox / Safari :</strong> mentné touch-a <em>Maj</em> (<em>Shift</em>) an klikan asou bouton-an <em>Atchwalizé</em> ou présé <em>Ctrl-F5</em> ou <em>Ctrl-R</em> (<em>⌘-R</em> asou roun Mac) \n* <strong>Google Chrome :</strong> apwiyé asou <em>Ctrl-Maj-R</em> (<em>⌘-Shift-R</em> asou roun Mac) \n* <strong>Internet Explorer :</strong> mentné touch-a <em>Ctrl</em> an klikan asou bouton-an <em>Atchwalizé</em> ou présé <em>Ctrl-F5</em> \n* <strong>Opera :</strong> alé annan <em>Menu → Settings</em> (<em>Opera → Préférences</em> asou roun Mac) é answit à <em>Konfidansyalité & sékrité → Éfasé doné d'èksplorasyon-yan → Imaj ké fiché an kach</em>.",
        "previewnote": "<strong>Raplé-zòt ki a jis roun prévizwalizasyon.</strong>\nZòt modifikasyon pa òkò anréjistré !",
        "sp-contributions-newonly": "Afiché inikman modifikasyon-yan ki sa dé kréyasyon di paj",
        "sp-contributions-submit": "Sasé",
        "whatlinkshere": "Paj lyé",
-       "whatlinkshere-title": "Paj ki ka pwenté vèr « $1 »",
+       "whatlinkshere-title": "Paj ki ka pwenté bò'd « $1 »",
        "whatlinkshere-page": "Paj :",
        "linkshere": "Paj-ya ki anba ka kontni roun lyen vèr <strong>[[:$1]]</strong> :",
        "nolinkshere": "Pyès paj pa gen kontni dé lyen vèr <strong>[[:$1]]</strong>.",
index 918e4bd..1c372e5 100644 (file)
        "customcssprotected": "Non ten os permisos necesarios para modificar esta páxina de CSS, dado que contén a configuración persoal doutro usuario.",
        "customjsprotected": "Non ten os permisos necesarios para modificar esta páxina de JavaScript, dado que contén a configuración persoal doutro usuario.",
        "mycustomcssprotected": "Non ten os permisos necesarios para editar esta páxina de CSS.",
+       "mycustomjsonprotected": "Non ten permisos para editar esta páxina JSON.",
        "mycustomjsprotected": "Non ten os permisos necesarios para editar esta páxina de JavaScript.",
        "myprivateinfoprotected": "Non ten os permisos necesarios para editar a súa información privada.",
        "mypreferencesprotected": "Non ten os permisos necesarios para editar as súas preferencias.",
        "wrongpasswordempty": "O campo do contrasinal estaba en branco.\nPor favor, inténteo de novo.",
        "passwordtooshort": "Os contrasinais deben conter, como mínimo, {{PLURAL:$1|1 carácter|$1 caracteres}}.",
        "passwordtoolong": "Os contrasinais non poden ser máis longo de {{PLURAL:$1|1 carácter|$1 caracteres}}.",
-       "passwordtoopopular": "Non pode utilizar un contrasinal dos habitualmente elixidos pola xente. Por favor, escolla un contrasinal máis orixinal.",
+       "passwordtoopopular": "Non pode utilizar un contrasinal dos habitualmente elixidos pola xente. Por favor, escolla un contrasinal que sexa máis complicada de adiviñar.",
        "password-name-match": "O seu contrasinal debe ser diferente do seu nome de usuario.",
        "password-login-forbidden": "O uso deste nome de usuario e contrasinal foi prohibido.",
        "mailmypassword": "Restablecer o contrasinal",
        "passwordremindertitle": "Novo contrasinal temporal para {{SITENAME}}",
-       "passwordremindertext": "Alguén (probablemente vostede, desde o enderezo IP $1) solicitou un novo\ncontrasinal para acceder a {{SITENAME}} ($4). Creouse un contrasinal temporal para o usuario\n\"$2\" e quedou establecido como \"$3\". Se esa foi a súa\nintención, terá que acceder ao sistema e escoller un novo contrasinal agora.\nO seu contrasinal temporal caducará {{PLURAL:$5|nun día|en $5 días}}.\n\nSe foi outra persoa a que fixo esta solicitude ou se xa se lembra do seu contrasinal\ne non o quere modificar, pode ignorar esta mensaxe e\ncontinuar a utilizar o seu contrasinal vello.",
+       "passwordremindertext": "Alguén (desde o enderezo IP $1) solicitou un novo\ncontrasinal para acceder a {{SITENAME}} ($4). Creouse un contrasinal temporal para o usuario\n\"$2\" e quedou establecido como \"$3\". Se esa foi a súa\nintención, terá que acceder ao sistema e escoller un novo contrasinal agora.\nO seu contrasinal temporal caducará {{PLURAL:$5|nun día|en $5 días}}.\n\nSe foi outra persoa a que fixo esta solicitude ou se xa se lembra do seu contrasinal\ne non o quere modificar, pode ignorar esta mensaxe e\ncontinuar a utilizar o seu contrasinal vello.",
        "noemail": "O usuario \"$1\" non posúe ningún enderezo de correo electrónico rexistrado.",
        "noemailcreate": "Ten que proporcionar un enderezo de correo electrónico válido",
        "passwordsent": "Enviouse un contrasinal novo ao enderezo de correo electrónico rexistrado de \"$1\".\nPor favor, acceda ao sistema de novo tras recibilo.",
        "sitecsspreview": "'''Lembre que só está vendo a vista previa deste CSS.'''\n'''Este aínda non foi gardado!'''",
        "sitejsonpreview": "<strong>Lembre que tan só está previsualizando esta configuración JSON.\nAínda non foi gardada!</strong>",
        "sitejspreview": "'''Lembre que só está vendo a vista previa deste código JavaScript.'''\n'''Este aínda non foi gardado!'''",
-       "userinvalidconfigtitle": "<strong>Aviso:</strong> Non hai ningunha aparencia chamada \"$1\".\nLembre que as páxinas .css e .js personalizadas utilizan un título en minúsculas, como por exemplo \"{{ns:user}}:Exemplo/vector.css\" no canto de \"{{ns:user}}:Exemplo/Vector.css\".",
+       "userinvalidconfigtitle": "<strong>Aviso:</strong> Non hai ningunha aparencia chamada \"$1\".\nLembre que as páxinas .css, .json e .js personalizadas utilizan un título en minúsculas, como por exemplo \"{{ns:user}}:Exemplo/vector.css\" no canto de \"{{ns:user}}:Exemplo/Vector.css\".",
        "updated": "(Actualizado)",
        "note": "'''Nota:'''",
        "previewnote": "<strong>Lembre que esta é só unha vista previa.</strong>\nAínda non gardou os seus cambios!",
        "longpageerror": "'''Erro: O texto que pretende gardar ocupa {{PLURAL:$1|$1 kilobyte|$1 kilobytes}}, e existe un límite dun máximo de {{PLURAL:$2|$2 kilobyte|$2 kilobytes}}.'''\nPolo tanto, non se pode gardar.",
        "readonlywarning": "<strong>Atención: Pechouse a base de datos para facer mantemento, polo que non vai poder gardar as súas edicións polo de agora.</strong>\nSe cadra, pode cortar e pegar o texto nun ficheiro de texto e gardalo para despois.\n\nO administrador do sistema que a pechou deu esta explicación: $1",
        "protectedpagewarning": "'''Aviso: Esta páxina foi protexida de xeito que só os usuarios con privilexios de administrador a poidan editar.'''\nVelaquí está a última entrada no rexistro, por se quere consultala:",
-       "semiprotectedpagewarning": "'''Nota:''' Esta páxina foi protexida de xeito que só os usuarios rexistrados a poidan editar.\nVelaquí está a última entrada no rexistro, por se quere consultala:",
+       "semiprotectedpagewarning": "<strong>Nota:</strong> Esta páxina foi protexida de xeito que só os usuarios autoconfirmados a poidan editar.\nVelaquí está a última entrada no rexistro, por se quere consultala:",
        "cascadeprotectedwarning": "<strong>Atención:</strong> Protexeuse esta páxina de xeito que só a poden editar os usuarios con [[Special:ListGroupRights|privilexios específicos]] debido a que está transcluída {{PLURAL:$1|na seguinte páxina protexida|nas seguintes páxinas protexidas}} coa opción \"protección en serie\" activada:",
        "titleprotectedwarning": "'''Aviso: Esta páxina foi protexida de xeito que [[Special:ListGroupRights|só algúns usuarios]] a poidan crear.'''\nVelaquí está a última entrada no rexistro, por se quere consultala:",
        "templatesused": "{{PLURAL:$1|Modelo usado|Modelos usados}} nesta páxina:",
        "prefs-dateformat": "Formato da data",
        "prefs-timeoffset": "Desprazamento horario",
        "prefs-advancedediting": "Opcións xerais",
+       "prefs-developertools": "Ferramentas de desenvolvemento",
        "prefs-editor": "Editor",
        "prefs-preview": "Vista previa",
        "prefs-advancedrc": "Opcións avanzadas",
        "rcfilters-filter-humans-description": "Edicións realizadas por editores humanos.",
        "rcfilters-filtergroup-reviewstatus": "Estado de revisión",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "Sen patrullar",
+       "rcfilters-filter-reviewstatus-manual-description": "Edicións marcadas manualmente como vixiadas.",
+       "rcfilters-filter-reviewstatus-manual-label": "Vixiadas manualmente",
+       "rcfilters-filter-reviewstatus-auto-description": "Edicións realizadas por usuarios avanzados cuxo traballo márcase automaticamente como vixiado.",
+       "rcfilters-filter-reviewstatus-auto-label": "Vixiado automaticamente",
        "rcfilters-filtergroup-significance": "Importancia",
        "rcfilters-filter-minor-label": "Edicións menores",
        "rcfilters-filter-minor-description": "Edicións que o autor etiquetou como menores.",
        "rollback-success": "Desfixéronse as edicións de {{GENDER:$3|$1}};\nvolveuse á última edición, feita por {{GENDER:$4|$2}}.",
        "rollback-success-notify": "Revertéronse as edicións de $1;\nrestaurouse a última revisión de $2. [$3 Mostrar os cambios]",
        "sessionfailure-title": "Erro de sesión",
-       "sessionfailure": "Parece que hai un problema co rexistro da súa sesión;\nesta acción cancelouse como precaución fronte ao secuestro de sesións.\nPrema no botón \"atrás\", volva cargar a páxina da que proviña e inténteo de novo.",
+       "sessionfailure": "Parece que hai un problema co rexistro da súa sesión;\nesta acción cancelouse como precaución fronte ao secuestro de sesións.\nPor favor, volva enviar o formulario.",
        "changecontentmodel": "Cambiar o modelo de contido dunha páxina",
        "changecontentmodel-legend": "Cambiar o modelo de contido",
        "changecontentmodel-title-label": "Título da páxina",
        "fix-double-redirects": "Actualizar calquera redirección que apunte cara ao título orixinal",
        "move-leave-redirect": "Deixar unha redirección detrás",
        "protectedpagemovewarning": "'''Aviso:''' Esta páxina foi protexida de xeito que só os usuarios con privilexios de administrador a poidan mover.\nVelaquí está a última entrada no rexistro, por se quere consultala:",
-       "semiprotectedpagemovewarning": "'''Nota:''' Esta páxina foi protexida de xeito que só os usuarios rexistrados a poidan mover.\nVelaquí está a última entrada no rexistro, por se quere consultala:",
+       "semiprotectedpagemovewarning": "<strong>Nota:</strong> Esta páxina foi protexida de xeito que só os usuarios autoconfirmados a poidan mover.\nVelaquí está a última entrada no rexistro, por se quere consultala:",
        "move-over-sharedrepo": "\"[[:$1]]\" xa existe nun repositorio compartido. Ao mover un ficheiro a este título sobrescribirase o ficheiro compartido.",
        "file-exists-sharedrepo": "O nome que elixiu para o ficheiro xa está en uso nun repositorio compartido.\nPor favor, escolla outro nome.",
        "export": "Exportar páxinas",
        "unlinkaccounts-success": "A conta foi desvinculada.",
        "authenticationdatachange-ignored": "Os cambios de datos de autenticación non foron xerados. Está configurado o provedor?",
        "userjsispublic": "Lembre: As subpáxinas JavaScript non deberían conter datos confidenciais porque outros usuarios poden velos.",
+       "userjsonispublic": "Por favor, teña en conta queː as subpáxinas JSON non deben conter datos confidenciais xa que son visibles por outros usuarios.",
        "usercssispublic": "Lembre: As subpáxinas CSS non deberían conter datos confidenciais porque outros usuarios poden velos.",
        "restrictionsfield-badip": "Enderezo IP ou rango de IP non válido: $1",
        "restrictionsfield-label": "Rangos de IP permitidos:",
index 1b82d03..408d8c9 100644 (file)
@@ -8,7 +8,7 @@
                        "Ammarpad"
                ]
        },
-       "tog-underline": "A shaya zaruruwa",
+       "tog-underline": "Link underlining:",
        "tog-hideminor": "A ɓoye ƙananan gyare-gyare na baya-bayan nan",
        "tog-hidepatrolled": "A ɓoye gyare-gyaren kan ido a cikin gyare-gyare bayan-bayan nan",
        "tog-newpageshidepatrolled": "A ɓoye shafuna kan ido a cikin sabbin shafuna",
        "missingarticle-rev": "(lambar zubi: $1)",
        "badtitletext": "Kan shafin da aka nema bai da ma'ana, ko kango ne, ko kuma wani kai ne na tsakanin harsuna ko shire-shire da bai da mahaɗi mai kyau.\nTana yiyuwa yana da harafi ko haruffa da ba su karɓuwa cikin kanu.",
        "viewsource": "Duba tushe",
+       "ns-specialprotected": "Shafuka na musamman ba za a iya gyra su ba.",
+       "logouttext": "Yanzu kun yi login out.",
+       "cannotlogoutnow-title": "Ba za ku iya login out ba yanzu. Ku sake gwadawa.",
+       "welcomeuser": "Barka da zuwa, $1!",
+       "welcomecreation-msg": "Yanzu kayi kirkiri sabon account.",
        "yourname": "Sunan ma'aikaci:",
        "userlogin-yourname": "Suna mai amfani",
        "userlogin-yourname-ph": "Shiga sunanka mai amfani",
        "logout": "Fita",
        "userlogout": "Fita",
        "createaccount": "ƙirƙira asusu",
+       "userlogin-resetpassword-link": "Ka manta lambobin sirrinka?",
        "createacct-emailrequired": "adireshin i-mel",
        "createacct-emailoptional": "adireshin i-mel (zaɓi)",
        "createacct-email-ph": "shiga adireshinka i-mel",
index 450c755..4a5a09d 100644 (file)
        "botpasswords-existing": "סיסמאות בוט קיימות",
        "botpasswords-createnew": "יצירת סיסמת בוט חדשה",
        "botpasswords-editexisting": "עריכת סיסמת בוט קיימת",
+       "botpasswords-label-needsreset": "(הסיסמה דורשת איפוס)",
        "botpasswords-label-appid": "שם הבוט:",
        "botpasswords-label-create": "יצירה",
        "botpasswords-label-update": "עדכון",
        "botpasswords-restriction-failed": "כניסה זו נמנעה בשל הגבלות על סיסמאות בוט.",
        "botpasswords-invalid-name": "שם המשתמש שניתן אינו מכיל את תו הפרדת סיסמאות הבוט (\"$1\").",
        "botpasswords-not-exist": "{{GENDER:$1|למשתמש|למשתמשת}} \"$1\" אין סיסמת בוט בשם \"$2\".",
+       "botpasswords-needs-reset": "נדרש איפוס של סיסמת הבוט עבור הבוט \"$2\" של {{GENDER:$1|המשתמש|המשתמשת}} \"$1\".",
        "resetpass_forbidden": "לא ניתן לשנות סיסמאות",
        "resetpass_forbidden-reason": "לא ניתן לשנות את הסיסמאות: $1",
        "resetpass-no-info": "נדרשת כניסה לחשבון כדי לגשת לדף זה באופן ישיר.",
        "rcfilters-savedqueries-remove": "הסרה",
        "rcfilters-savedqueries-new-name-label": "שם",
        "rcfilters-savedqueries-new-name-placeholder": "תיאור מטרת המסנן",
-       "rcfilters-savedqueries-apply-label": "יצירת מסנן",
+       "rcfilters-savedqueries-apply-label": "×\99צ×\99רת ×\94×\9eסנ×\9f",
        "rcfilters-savedqueries-apply-and-setdefault-label": "יצירת מסנן התחלתי",
        "rcfilters-savedqueries-cancel-label": "ביטול",
        "rcfilters-savedqueries-add-new-title": "שמירת הגדרות המסננים הנוכחיות",
        "rcfilters-filter-user-experience-level-unregistered-label": "לא רשומים",
        "rcfilters-filter-user-experience-level-unregistered-description": "עורכים שלא נכנסו לחשבון.",
        "rcfilters-filter-user-experience-level-newcomer-label": "חדשים",
-       "rcfilters-filter-user-experience-level-newcomer-description": "עורכים רשומים עם פחות מ־10 עריכות או 4 ימים של פעילות.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "עורכים רשומים עם פחות מ־10 עריכות או פחות מ־4 ימים של פעילות.",
        "rcfilters-filter-user-experience-level-learner-label": "לומדים",
        "rcfilters-filter-user-experience-level-learner-description": "עורכים רשומים שרמת הניסיון שלהם היא בין \"חדשים\" לבין \"מנוסים\".",
        "rcfilters-filter-user-experience-level-experienced-label": "משתמשים מנוסים",
-       "rcfilters-filter-user-experience-level-experienced-description": "עורכים רשומים עם יותר מ־500 עריכות ו־30 ימים של פעילות.",
+       "rcfilters-filter-user-experience-level-experienced-description": "עורכים רשומים עם יותר מ־500 עריכות ויותר מ־30 ימים של פעילות.",
        "rcfilters-filtergroup-automated": "תרומות אוטומטיות",
        "rcfilters-filter-bots-label": "בוטים",
        "rcfilters-filter-bots-description": "עריכות שבוצעו על־ידי כלים אוטומטיים.",
        "rcfilters-filter-humans-label": "בני אדם (לא בוטים)",
        "rcfilters-filter-humans-description": "עריכות שבוצעו על־ידי עורכים אנושיים.",
-       "rcfilters-filtergroup-reviewstatus": "×\9eצ×\91 ×¡×§×\99רה",
+       "rcfilters-filtergroup-reviewstatus": "×\9eצ×\91 ×\91×\93×\99קה",
        "rcfilters-filter-reviewstatus-unpatrolled-description": "עריכות שלא סומנו כבדוקות באופן ידני או באופן אוטומטי.",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "לא בדוקות",
        "rcfilters-filter-reviewstatus-manual-description": "עריכות שסומנו כבדוקות באופן ידני.",
        "rcfilters-filter-categorization-description": "רישומים על דפים שנוספו לקטגוריות או הוסרו מהן.",
        "rcfilters-filter-logactions-label": "פעולות יומן",
        "rcfilters-filter-logactions-description": "פעולות מנהליות, יצירת חשבונות, מחיקת דפים, העלאות…",
-       "rcfilters-hideminor-conflicts-typeofchange-global": "×\9eסנ×\9f \"ער×\99×\9b×\95ת ×\9eשנ×\99×\95ת\" ×\9eתנ×\92ש ×¢×\9d ×\9eסנ×\9f ×¡×\95×\92 ×\94ש×\99× ×\95×\99×\99×\9d ×\90×\97×\93 ×\90×\95 ×\99×\95תר, כי סוגים מסוימים של שינויים אינם יכולים להיות מסווגים בתור \"משניים\". המסננים המתנגשים מסומנים באזור המסננים הפעילים לעיל.",
+       "rcfilters-hideminor-conflicts-typeofchange-global": "×\9eסנ×\9f \"ער×\99×\9b×\95ת ×\9eשנ×\99×\95ת\" ×\9eתנ×\92ש ×¢×\9d ×\9eסנ×\9f ×\90×\97×\93 ×\90×\95 ×\99×\95תר ×©×\9c ×¡×\95×\92 ×\94ש×\99× ×\95×\99×\99×\9d, כי סוגים מסוימים של שינויים אינם יכולים להיות מסווגים בתור \"משניים\". המסננים המתנגשים מסומנים באזור המסננים הפעילים לעיל.",
        "rcfilters-hideminor-conflicts-typeofchange": "סוגים מסוימים של שינויים אינם יכולים להיות מסווגים כ\"משניים\", כך שמסנן זה מתנגש עם מסנן סוג השינויים הבא: $1",
        "rcfilters-typeofchange-conflicts-hideminor": "מסנן סוג השינויים הזה מתנגש עם מסנן \"עריכות משניות\". סוגים מסוימים של שינויים אינם יכולים מסווגים כ\"משניים\".",
        "rcfilters-filtergroup-lastRevision": "גרסאות אחרונות",
        "rcfilters-filter-previousrevision-description": "כל השינויים שאינם \"הגרסה האחרונה\".",
        "rcfilters-filter-excluded": "מוחרג",
        "rcfilters-tag-prefix-namespace-inverted": "<strong>:לא</strong> $1",
-       "rcfilters-exclude-button-off": "×\9c×\94×\97ר×\99×\92 ×\90ת המסומנים",
-       "rcfilters-exclude-button-on": "ללא המסומנים",
+       "rcfilters-exclude-button-off": "×\9c×\9c×\90 ×\94×\9eר×\97×\91×\99×\9d המסומנים",
+       "rcfilters-exclude-button-on": "×\9c×\9c×\90 ×\94×\9eר×\97×\91×\99×\9d ×\94×\9eס×\95×\9e× ×\99×\9d",
        "rcfilters-view-tags": "עריכות מתויגות",
        "rcfilters-view-namespaces-tooltip": "סינון התוצאות לפי מרחב שם",
        "rcfilters-view-tags-tooltip": "סינון התוצאות לפי תגיות עריכה",
        "rcfilters-liveupdates-button-title-off": "הצגת שינויים חדשים כשהם מתרחשים",
        "rcfilters-watchlist-markseen-button": "סימון כל השינויים כאילו נצפו",
        "rcfilters-watchlist-edit-watchlist-button": "עריכת רשימת הדפים במעקב שלך",
-       "rcfilters-watchlist-showupdated": "ש×\99× ×\95×\99×\99×\9d ×\91×\93פ×\99×\9d ×©×\9c×\90 ×\91×\99קרת ×\91×\94×\9d ×\9e×\90×\96 ×\91×\99צ×\95×¢ ×\94ש×\99× ×\95×\99×\99×\9d ×\9e×\95פ×\99×¢×\99×\9d ×\91×\9bת×\91 <strong>×\9e×\95×\93×\92ש</strong>, ×\95×\9e×\95×\93×\92שים בצבע.",
+       "rcfilters-watchlist-showupdated": "ש×\99× ×\95×\99×\99×\9d ×\91×\93פ×\99×\9d ×©×\9c×\90 ×\91×\99קרת ×\91×\94×\9d ×\9e×\90×\96 ×\91×\99צ×\95×¢ ×\94ש×\99× ×\95×\99×\99×\9d ×\9e×\95פ×\99×¢×\99×\9d ×\91×\9bת×\91 <strong>×\9e×\95×\93×\92ש</strong>, ×\95×\9eס×\95×\9e× ים בצבע.",
        "rcfilters-preference-label": "הסתרת הגרסה המשופרת של השינויים האחרונים",
        "rcfilters-preference-help": "ביטול של העיצוב מחדש של הממשק (שבוצע בשנת 2017) ושל כל הכלים שנוספו אז ומאז.",
        "rcfilters-filter-showlinkedfrom-label": "הצגת שינויים בדפים שמקושרים מתוך",
        "rcfilters-filter-showlinkedfrom-option-label": "<strong>דפים שמקושרים מתוך</strong> הדף שנבחר",
        "rcfilters-filter-showlinkedto-label": "הצגת שינויים בדפים שמקשרים אל",
        "rcfilters-filter-showlinkedto-option-label": "<strong>דפים שמקשרים אל</strong> הדף שנבחר",
-       "rcfilters-target-page-placeholder": "×\94ק×\9c×\93ת שם דף (או קטגוריה)",
+       "rcfilters-target-page-placeholder": "×\99ש ×\9c×\94ק×\9c×\99×\93 שם דף (או קטגוריה)",
        "rcnotefrom": "להלן {{PLURAL:$5|השינוי שבוצע|השינויים שבוצעו}} מאז <strong>$3, $4</strong> (מוצגים עד <strong>$1</strong>).",
        "rclistfromreset": "איפוס בחירת התאריך",
        "rclistfrom": "הצגת שינויים חדשים החל מ־$2, $3",
        "recentchangeslinked-feed": "שינויים בדפים המקושרים",
        "recentchangeslinked-toolbox": "שינויים בדפים המקושרים",
        "recentchangeslinked-title": "שינויים בדפים המקושרים מהדף \"$1\"",
-       "recentchangeslinked-summary": "יש להקליד שם דף כדי לראות את השינויים בדפים המקשרים לדף זה או המקושרים ממנו. (כדי לראות את הדפים החברים בקטגוריה, יש להקליד \"קטגוריה:שם הקטגוריה\".) שינויים בדפים ב[[Special:Watchlist|רשימת המעקב שלך]] מוצגים ב<strong>הדגשה</strong>.",
+       "recentchangeslinked-summary": "יש להקליד שם דף כדי לראות את השינויים בדפים המקשרים לדף זה או המקושרים ממנו. (כדי לראות את הדפים החברים בקטגוריה, יש להקליד \"{{ns:category}}:שם הקטגוריה\".) שינויים בדפים ב[[Special:Watchlist|רשימת המעקב שלך]] מוצגים ב<strong>הדגשה</strong>.",
        "recentchangeslinked-page": "שם הדף:",
        "recentchangeslinked-to": "הצגת השינויים בדפים המקשרים לדף הנתון במקום זאת",
        "recentchanges-page-added-to-category": "הדף [[:$1]] נוסף לקטגוריה",
        "recentchanges-page-added-to-category-bundled": "הדף [[:$1]] נוסף לקטגוריה, [[Special:WhatLinksHere/$1|והוא מוכלל בדפים אחרים]]",
        "recentchanges-page-removed-from-category": "הדף [[:$1]] הוסר מהקטגוריה",
-       "recentchanges-page-removed-from-category-bundled": "הדף [[:$1]] הוסר מהקטגוריה, ו[[Special:WhatLinksHere/$1|הוא מוכלל בדפים אחרים]]",
+       "recentchanges-page-removed-from-category-bundled": "הדף [[:$1]] הוסר מהקטגוריה, [[Special:WhatLinksHere/$1|והוא מוכלל בדפים אחרים]]",
        "autochange-username": "שינוי אוטומטי של מדיה־ויקי",
        "upload": "העלאת קובץ",
        "uploadbtn": "העלאת הקובץ",
-       "reuploaddesc": "×\91×\99×\98×\95×\9c ×\94×\94×¢×\9c×\90×\94 ×\95×\97×\96ר×\94 ×\9c×\98×\95פס ×\94×¢×\9c×\90ת ×§×\91צ×\99×\9d ×\9cשרת",
+       "reuploaddesc": "×\91×\99×\98×\95×\9c ×\94×\94×¢×\9c×\90×\94 ×\95×\97×\96ר×\94 ×\9c×\98×\95פס ×\94×¢×\9c×\90ת ×\94ק×\91צ×\99×\9d",
        "upload-tryagain": "שליחת התיאור החדש של הקובץ",
        "upload-tryagain-nostash": "שליחת הקובץ המועלה מחדש והתיאור המעודכן",
        "uploadnologin": "לא נכנסת לחשבון",
        "uploadnologintext": "נדרשת $1 כדי להעלות קבצים.",
        "upload_directory_missing": "שרת האינטרנט אינו יכול ליצור את תיקיית ההעלאות ($1) החסרה.",
        "upload_directory_read_only": "שרת האינטרנט אינו יכול לכתוב בתיקיית ההעלאות ($1).",
-       "uploaderror": "ש×\92×\99×\90×\94 ×\91×\94×¢×\9c×\90ת ×\94ק×\95×\91×¥",
-       "upload-recreate-warning": "'''אזהרה: קובץ בשם זה נמחק או הועבר.'''\n\nיומני המחיקות וההעברות של הדף מוצגים להלן:",
-       "uploadtext": "×\94שת×\9eש×\95 ×\91×\98×\95פס ×\9c×\94×\9c×\9f ×\9b×\93×\99 ×\9c×\94×¢×\9c×\95ת ×§×\91צ×\99×\9d.\n×\9b×\93×\99 ×\9cר×\90×\95ת ×\90×\95 ×\9c×\97פש ×§×\91צ×\99×\9d ×©×\94×\95×¢×\9c×\95 ×\91×¢×\91ר ×\90× ×\90 ×¤× ×\95 ×\9c[[Special:FileList|רש×\99×\9eת ×\94ק×\91צ×\99×\9d ×©×\94×\95×¢×\9c×\95]], ×\95×\9b×\9e×\95 ×\9b×\9f, ×\94×¢×\9c×\90×\95ת (×\9b×\95×\9c×\9c ×\94×¢×\9c×\90×\95ת ×©×\9c ×\92רס×\94 ×\97×\93ש×\94) ×\9e×\95צ×\92×\95ת ×\91[[Special:Log/upload|×\99×\95×\9e×\9f ×\94×\94×¢×\9c×\90×\95ת]], ×\95×\9e×\97×\99ק×\95ת ×\91[[Special:Log/delete|×\99×\95×\9e×\9f ×\94×\9e×\97×\99ק×\95ת]].\n\n×\9b×\93×\99 ×\9c×\9b×\9c×\95×\9c ×§×\95×\91×¥ ×\91×\93×£, ×\94שת×\9eש×\95 ×\91ק×\99ש×\95ר ×\91×\90×\97ת ×\94צ×\95ר×\95ת ×\94×\91×\90×\95ת:\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.jpg]]</nowiki></code>''' ×\9cש×\99×\9e×\95ש ×\91×\92רס×\94 ×\94×\9e×\9c×\90×\94 ×©×\9c ×\94ק×\95×\91×¥\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.png|200px|thumb|left|×\98קס×\98 ×ª×\99×\90×\95ר]]</nowiki></code>''' ×\9cש×\99×\9e×\95ש ×\91×\92רס×\94 ×\9e×\95ק×\98נת ×\91ר×\95×\97×\91 200 ×¤×\99קס×\9c×\99×\9d ×\91ת×\99×\91×\94 ×\91צ×\93 ×©×\9e×\90×\9c ×©×\9c ×\94×\93×£, ×¢×\9d '×\98קס×\98 ×ª×\99×\90×\95ר' ×\9bת×\99×\90×\95ר\n* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code>''' לקישור ישיר לקובץ בלי להציגו",
+       "uploaderror": "ש×\92×\99×\90×\94 ×\91×\94×¢×\9c×\90×\94",
+       "upload-recreate-warning": "<strong>אזהרה: קובץ בשם זה נמחק או הועבר.</strong>\n\nיומני המחיקות וההעברות של הדף מוצגים להלן:",
+       "uploadtext": "× ×\99ת×\9f ×\9c×\94שת×\9eש ×\91×\98×\95פס ×©×\9c×\94×\9c×\9f ×\9b×\93×\99 ×\9c×\94×¢×\9c×\95ת ×§×\91צ×\99×\9d.\n×\91×\90פשר×\95ת×\9a ×\9cר×\90×\95ת ×\90×\95 ×\9c×\97פש ×§×\91צ×\99×\9d ×©×\94×\95×¢×\9c×\95 ×\91×¢×\91ר ×\91[[Special:FileList|רש×\99×\9eת ×\94ק×\91צ×\99×\9d ×©×\94×\95×¢×\9c×\95]]. ×\9b×\9e×\95Ö¾×\9b×\9f, ×\94×¢×\9c×\90×\95ת (×\9b×\95×\9c×\9c ×\94×¢×\9c×\90×\95ת ×©×\9c ×\92רס×\94 ×\97×\93ש×\94) ×\9e×\95צ×\92×\95ת ×\91[[Special:Log/upload|×\99×\95×\9e×\9f ×\94×\94×¢×\9c×\90×\95ת]], ×\95×\9e×\97×\99ק×\95ת ×\9e×\95צ×\92×\95ת ×\91[[Special:Log/delete|×\99×\95×\9e×\9f ×\94×\9e×\97×\99ק×\95ת]].\n\n×\9b×\93×\99 ×\9c×\9b×\9c×\95×\9c ×§×\95×\91×¥ ×\91×\93×£, ×\99ש ×\9c×\94שת×\9eש ×\91ק×\99ש×\95ר ×\91×\90×\97ת ×\94צ×\95ר×\95ת ×\94×\91×\90×\95ת:\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.jpg]]</nowiki></code></strong> ×\9cש×\99×\9e×\95ש ×\91×\92רס×\94 ×\94×\9e×\9c×\90×\94 ×©×\9c ×\94ק×\95×\91×¥\n* <strong><code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:File.png|200px|thumb|left|×\98קס×\98 ×ª×\99×\90×\95ר]]</nowiki></code></strong> ×\9cש×\99×\9e×\95ש ×\91×\92רס×\94 ×\9e×\95ק×\98נת ×\91ר×\95×\97×\91 200 ×¤×\99קס×\9c×\99×\9d ×\91ת×\99×\91×\94 ×\91צ×\93 ×©×\9e×\90×\9c ×©×\9c ×\94×\93×£ ×¢×\9d ×\98קס×\98 ×\9cת×\99×\90×\95ר\n* <strong><code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:File.ogg]]</nowiki></code></strong> לקישור ישיר לקובץ בלי להציגו",
        "upload-permitted": "{{PLURAL:$2|סוג קובץ מותר|סוגי קבצים מותרים}}: $1.",
        "upload-preferred": "{{PLURAL:$2|סוג קובץ מומלץ|סוגי קבצים מומלצים}}: $1.",
        "upload-prohibited": "{{PLURAL:$2|סוג קובץ אסור|סוגי קבצים אסורים}}: $1.",
        "uploadlogpage": "יומן העלאות",
-       "uploadlogpagetext": "×\9c×\94×\9c×\9f ×¨×©×\99×\9e×\94 ×©×\9c ×\94×¢×\9c×\90×\95ת ×\94ק×\91צ×\99×\9d ×\94×\90×\97ר×\95× ×\95ת ×©×\91×\95צע×\95.\nר×\90×\95 ×\90ת [[Special:NewFiles|גלריית הקבצים החדשים]] להצגה ויזואלית שלהם.",
+       "uploadlogpagetext": "×\9c×\94×\9c×\9f ×¨×©×\99×\9e×\94 ×©×\9c ×\94×¢×\9c×\90×\95ת ×\94ק×\91צ×\99×\9d ×\94×\90×\97ר×\95× ×\95ת ×©×\91×\95צע×\95.\n×\90פשר ×\9c×¢×\99×\99×\9f ×\91[[Special:NewFiles|גלריית הקבצים החדשים]] להצגה ויזואלית שלהם.",
        "filename": "שם הקובץ",
        "filedesc": "תקציר",
        "fileuploadsummary": "תיאור:",
        "ignorewarning": "התעלמות מהאזהרה ושמירת הקובץ בכל זאת",
        "ignorewarnings": "התעלמות מכל האזהרות",
        "minlength1": "שמות קבצים צריכים להיות בני תו אחד לפחות.",
-       "illegalfilename": "ש×\9d ×\94ק×\95×\91×¥ \"$1\" ×\9e×\9b×\99×\9c ×ª×\95×\95×\99×\9d ×©×\90×\99× ×\9d ×\9e×\95תר×\99×\9d ×\91×\9b×\95תר×\95ת ×\93פ×\99×\9d.\n× ×\90 ×\9cשנ×\95ת ×\90ת ×\94ש×\9d ולנסות להעלותו שנית.",
+       "illegalfilename": "ש×\9d ×\94ק×\95×\91×¥ \"$1\" ×\9e×\9b×\99×\9c ×ª×\95×\95×\99×\9d ×©×\90×\99× ×\9d ×\9e×\95תר×\99×\9d ×\91×\9b×\95תר×\95ת ×\93פ×\99×\9d.\n× ×\90 ×\9cשנ×\95ת ×\90ת ×©×\9d ×\94ק×\95×\91×¥ ולנסות להעלותו שנית.",
        "filename-toolong": "שמות של קבצים לא יכולים להיות ארוכים יותר מ־240 בתים.",
        "badfilename": "שם הקובץ שונה ל־\"$1\".",
-       "filetype-mime-mismatch": "סיומת הקובץ \".$1\" אינה מתאימה לסוג ה־MIME שנמצא לקובץ זה ($2).",
+       "filetype-mime-mismatch": "סיומת הקובץ \"<span dir=\"ltr\">.$1</span>\" אינה מתאימה לסוג ה־MIME שנמצא לקובץ זה ($2).",
        "filetype-badmime": "לא ניתן להעלות קבצים שסוג ה־MIME שלהם הוא \"$1\".",
        "filetype-bad-ie-mime": "לא ניתן להעלות קובץ זה, כיוון שאינטרנט אקספלורר יזהה אותו כקובץ מסוג \"$1\", שהוא סוג קובץ אסור שעלול להיות מסוכן.",
-       "filetype-unwanted-type": "'''\".$1\"''' הוא סוג קובץ בלתי מומלץ.\n{{PLURAL:$3|סוג הקובץ המומלץ הוא|סוגי הקבצים המומלצים הם}} $2.",
-       "filetype-banned-type": "'''\".$1\"''' {{PLURAL:$4|הוא סוג קובץ אסור להעלאה|הם סוגי קבצים אסורים להעלאה}}.\n{{PLURAL:$3|סוג הקובץ המותר הוא|סוגי הקבצים המותרים הם}} $2.",
+       "filetype-unwanted-type": "<strong dir=\"ltr\">\".$1\"</strong> הוא סוג קובץ בלתי־מומלץ.\n{{PLURAL:$3|סוג הקובץ המומלץ הוא|סוגי הקבצים המומלצים הם}} $2.",
+       "filetype-banned-type": "<strong dir=\"ltr\">\".$1\"</strong> {{PLURAL:$4|הוא סוג קובץ אסור להעלאה|הם סוגי קבצים אסורים להעלאה}}.\n{{PLURAL:$3|סוג הקובץ המותר הוא|סוגי הקבצים המותרים הם}} $2.",
        "filetype-missing": "לקובץ אין סיומת (כדוגמת \"<span dir=\"ltr\">.jpg</span>\").",
-       "empty-file": "הקובץ ששלחת היה ריק",
-       "file-too-large": "הקובץ ששלחת היה גדול מדי",
-       "filename-tooshort": "שם הקובץ קצר מדי",
+       "empty-file": "הקובץ ששלחת היה ריק.",
+       "file-too-large": "הקובץ ששלחת היה גדול מדי.",
+       "filename-tooshort": "שם הקובץ קצר מדי.",
        "filetype-banned": "אסור להעלות קבצים מהסוג הזה.",
-       "verification-error": "קובץ זה לא עבר את תהליך אימות הקבצים",
+       "verification-error": "קובץ זה לא עבר את תהליך אימות הקבצים.",
        "hookaborted": "השינוי שניסית לבצע הופסק על־ידי הרחבה.",
-       "illegal-filename": "שם הקובץ אינו מותר להעלאה",
-       "overwrite": "דריסת קובץ קיים אינה מותרת",
-       "unknown-error": "אירעה שגיאה בלתי ידועה",
-       "tmp-create-error": "לא ניתן ליצור קובץ זמני",
-       "tmp-write-error": "שגיאה בכתיבה לקובץ הזמני",
-       "large-file": "מומלץ שהקבצים לא יהיו גדולים יותר מ־$1 (גודל הקובץ שהעליתם הוא $2).",
+       "illegal-filename": "שם הקובץ אינו מותר להעלאה.",
+       "overwrite": "דריסת קובץ קיים אינה מותרת.",
+       "unknown-error": "אירעה שגיאה בלתי־ידועה.",
+       "tmp-create-error": "לא ניתן ליצור קובץ זמני.",
+       "tmp-write-error": "שגיאה בכתיבה לקובץ הזמני.",
+       "large-file": "מומלץ שהקבצים לא יהיו גדולים יותר מ{{GRAMMAR:תחילית|$1}}\n(גודל הקובץ שהעלית הוא $2).",
        "largefileserver": "גודל הקובץ חורג ממגבלת השרת.",
        "emptyfile": "נראה שהקובץ שהעלית ריק.\nייתכן שהסיבה לכך היא שגיאת הקלדה בשם הקובץ.\nיש לוודא שזה הקובץ שברצונך להעלות.",
        "windows-nonascii-filename": "אתר ויקי זה אינו תומך בשמות קבצים עם תווים מיוחדים או תווים שאינם באנגלית.",
-       "fileexists": "קובץ בשם הזה כבר קיים, אנא בִּדקו את <strong>[[:$1]]</strong> אם אינכם בטוחים שברצונכם להחליף אותו.\n[[$1|thumb]]",
-       "filepageexists": "×\93×£ ×ª×\99×\90×\95ר ×\94ק×\95×\91×¥ ×¢×\91×\95ר ×§×\95×\91×¥ ×\96×\94 ×\9b×\91ר × ×\95צר ×\91<strong>[[:$1]]</strong>, ×\90×\9a ×\9c×\90 ×§×\99×\99×\9d ×§×\95×\91×¥ ×\91ש×\9d ×\96×\94.\nת×\99×\90×\95ר ×\94ק×\95×\91×¥ ×©×ª×\9bת×\91×\95 ×\9c×\90 ×\99×\95פ×\99×¢ ×\91×\93×£ ×ª×\99×\90×\95ר ×\94ק×\95×\91×¥.\n×\9b×\93×\99 ×\9c×\92ר×\95×\9d ×\9c×\95 ×\9c×\94×\95פ×\99×¢ ×©×\9d, ×\99×\94×\99×\94 ×¢×\9c×\99×\9b×\9d ×\9cער×\95×\9a ×\90×\95ת×\95 ×\99×\93× ×\99ת. [[$1|thumb]]",
+       "fileexists": "קובץ בשם הזה כבר קיים, אנא {{GENDER:|בדוק|בדקי|בדקו}} את <strong>[[:$1]]</strong> אם {{GENDER:|אינך בטוח שברצונך|אינך בטוחה שברצונך|אינכם בטוחים שברצונכם}} להחליף אותו.\n[[$1|thumb]]",
+       "filepageexists": "×\93×£ ×\94ת×\99×\90×\95ר ×¢×\91×\95ר ×§×\95×\91×¥ ×\96×\94 ×\9b×\91ר × ×\95צר ×\91×\93×£ <strong>[[:$1]]</strong>, ×\90×\9a ×\9c×\90 ×§×\99×\99×\9d ×§×\95×\91×¥ ×\91ש×\9d ×\96×\94.\nת×\99×\90×\95ר ×\94ק×\95×\91×¥ ×©×\99×\99×\9bת×\91 ×\9b×\90×\9f ×\9c×\90 ×\99×\95פ×\99×¢ ×\91×\93×£ ×ª×\99×\90×\95ר ×\94ק×\95×\91×¥.\n×\9b×\93×\99 ×\9c×\92ר×\95×\9d ×\9c×\95 ×\9c×\94×\95פ×\99×¢ ×©×\9d, ×\99×\94×\99×\94 ×¦×\95ר×\9a ×\9cער×\95×\9a ×\90×\95ת×\95 ×\99×\93× ×\99ת.\n[[$1|thumb]]",
        "fileexists-extension": "קובץ עם שם דומה כבר קיים: [[$2|thumb]]\n* שם הקובץ המועלה: <strong>[[:$1]]</strong>\n* שם הקובץ הקיים: <strong>[[:$2]]</strong>\nאולי כדאי לתת לקובץ שם ספציפי יותר?",
        "fileexists-thumbnail-yes": "נראה שהקובץ הוא תמונה מוקטנת (ממוזערת).\n[[$1|thumb]]\nיש לבדוק את הקובץ <strong>[[:$1]]</strong>.\nאם הקובץ שבדקת הוא אותה התמונה בגודל מקורי, אין זה הכרחי להעלות גם תמונה ממוזערת.",
        "file-thumbnail-no": "שם הקובץ מתחיל ב־<strong>$1</strong>.\nנראה שזוהי תמונה מוקטנת (ממוזערת).\nאם התמונה בגודל מלא מצויה ברשותך, יש להעלות אותה ולא את התמונה הממוזערת; אחרת, יש לשנות את שם הקובץ.",
-       "fileexists-forbidden": "ק×\95×\91×¥ ×\91ש×\9d ×\96×\94 ×\9b×\91ר ×§×\99×\99×\9d, ×\95×\90×\99× ×\9b×\9d ×\99×\9b×\95×\9c×\99×\9d ×\9c×\94×\97×\9c×\99×£ ×\90×\95ת×\95.\n×\90×\9d ×\90ת×\9d ×¢×\93×\99×\99×\9f ×\9e×¢×\95× ×\99×\99× ×\99×\9d ×\9c×\94×¢×\9c×\95ת ×§×\95×\91×¥ ×\96×\94, ×\90× ×\90 ×\97×\96ר×\95 ×\9c×\93×£ ×\94ק×\95×\93×\9d ×\95×\94×¢×\9c×\95 את הקובץ תחת שם חדש.\n[[File:$1|thumb|center|$1]]",
-       "fileexists-shared-forbidden": "ק×\95×\91×¥ ×\91ש×\9d ×\96×\94 ×\9b×\91ר ×§×\99×\99×\9d ×\9bק×\95×\91×¥ ×\9eש×\95תף.\n×\90×\9d ×\90ת×\9d ×¢×\93×\99×\99×\9f ×\9e×¢×\95× ×\99×\99× ×\99×\9d ×\9c×\94×¢×\9c×\95ת ×§×\95×\91×¥ ×\96×\94, ×\90× ×\90 ×\97×\96ר×\95 ×\9c×\93×£ ×\94ק×\95×\93×\9d ×\95×\94×¢×\9c×\95 את הקובץ תחת שם חדש.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-forbidden": "ק×\95×\91×¥ ×\91ש×\9d ×\96×\94 ×\9b×\91ר ×§×\99×\99×\9d, ×\95×\9c×\90 × ×\99ת×\9f ×\9c×\94×\97×\9c×\99×£ ×\90×\95ת×\95.\n×\90×\9d ×¢×\93×\99×\99×\9f ×\91רצ×\95× ×\9a ×\9c×\94×¢×\9c×\95ת ×§×\95×\91×¥ ×\96×\94, ×\99ש ×\9c×\97×\96×\95ר ×\9c×\93×£ ×\94ק×\95×\93×\9d ×\95×\9c×\94×¢×\9c×\95ת את הקובץ תחת שם חדש.\n[[File:$1|thumb|center|$1]]",
+       "fileexists-shared-forbidden": "ק×\95×\91×¥ ×\91ש×\9d ×\96×\94 ×\9b×\91ר ×§×\99×\99×\9d ×\9bק×\95×\91×¥ ×\9eש×\95תף.\n×\90×\9d ×¢×\93×\99×\99×\9f ×\91רצ×\95× ×\9a ×\9c×\94×¢×\9c×\95ת ×§×\95×\91×¥ ×\96×\94, ×\99ש ×\9c×\97×\96×\95ר ×\9c×\93×£ ×\94ק×\95×\93×\9d ×\95×\9c×\94×¢×\9c×\95ת את הקובץ תחת שם חדש.\n[[File:$1|thumb|center|$1]]",
        "fileexists-no-change": "הקובץ שהועלה הוא העתק מדויק של הגרסה הנוכחית של <strong>[[:$1]]</strong>.",
        "fileexists-duplicate-version": "הקובץ שהועלה הוא העתק מדויק של {{PLURAL:$2|גרסה קודמת|גרסאות קודמות}} של <strong>[[:$1]]</strong>.",
        "file-exists-duplicate": "קובץ זה זהה {{PLURAL:$1|לקובץ הבא|לקבצים הבאים}}:",
        "file-deleted-duplicate": "קובץ זהה לקובץ זה ([[:$1]]) נמחק בעבר.\nיש לבדוק את היסטוריית המחיקה של הקובץ לפני העלאתו מחדש.",
        "file-deleted-duplicate-notitle": "קובץ זהה לקובץ זה נמחק בעבר, והכותרת שלו הועלמה.\nיש לבקש ממשתמש שיכול לראות נתונים על קבצים שהועלמו לבדוק את המצב לפני העלאת הקובץ מחדש.",
-       "uploadwarning": "×\90×\96×\94רת ×\94×¢×\9c×\90ת ×§×\91צ×\99×\9d",
-       "uploadwarning-text": "×\90× ×\90 ×©× ×\95 ×\90ת ×ª×\99×\90×\95ר ×\94ק×\95×\91×¥ ×©×\9c×\9e×\98×\94 ×\95נס×\95 שוב.",
+       "uploadwarning": "×\90×\96×\94רת ×\94×¢×\9c×\90×\94",
+       "uploadwarning-text": "× ×\90 ×\9cשנ×\95ת ×\90ת ×ª×\99×\90×\95ר ×\94ק×\95×\91×¥ ×©×\9c×\9e×\98×\94 ×\95×\9cנס×\95ת שוב.",
        "uploadwarning-text-nostash": "יש להעלות מחדש את הקובץ, לשנות את התיאור להלן ולנסות שוב.",
        "savefile": "שמירת קובץ",
        "uploaddisabled": "העלאת קבצים מבוטלת.",
        "copyuploaddisabled": "העלאת קבצים מכתובת URL מבוטלת.",
        "uploaddisabledtext": "אפשרות העלאת הקבצים מבוטלת.",
-       "php-uploaddisabledtext": "אפשרות העלאת הקבצים מבוטלת ברמת PHP. אנא בדקו את ההגדרה file_uploads.",
+       "php-uploaddisabledtext": "אפשרות העלאת הקבצים מבוטלת ברמת PHP.\nנא לבדוק את ההגדרה file_uploads.",
        "uploadscripted": "הקובץ כולל קוד סקריפט או HTML שעשוי להתפרש או להתבצע בטעות על־ידי הדפדפן.",
        "upload-scripted-pi-callback": "לא ניתן להעלות קובץ שמכיל את הוראת העיבוד XML-stylesheet.",
-       "upload-scripted-dtd": "לא ניתן להעלות קבצי SVG שכוללים הכרזת DTD לא־סטנדרטית.",
+       "upload-scripted-dtd": "×\9c×\90 × ×\99ת×\9f ×\9c×\94×¢×\9c×\95ת ×§×\95×\91צ×\99 SVG ×©×\9b×\95×\9c×\9c×\99×\9d ×\94×\9bר×\96ת DTD ×\9c×\90־ס×\98× ×\93ר×\98×\99ת.",
        "uploaded-script-svg": "נמצא אלמנט שאפשר לכתוב בו תסריט \"$1\" בקובץ ה־SVG שהועלה.",
        "uploaded-hostile-svg": "נמצא CSS בלתי־מאובטח באלמנט style בקובץ ה־SVG שהועלה.",
        "uploaded-event-handler-on-svg": "אסור להגדיר מאפייני טיפול באירועים <code dir=\"ltr\">$1=\"$2\"</code> בקובצי SVG.",
        "uploaded-href-attribute-svg": "רכיבי <a> יכולים לקשר (href) רק ליעדי data:‎ (קובץ מוטמע), http://‎ או https://‎, או מקטע (עם #, באותו מסמך). ברכיבים אחרים, כגון <image>, מותרים רק יעדי data:‎ ומקטע. באפשרותך לנסות להטמיע תמונות בעת ייצוא קובץ ה־SVG שלך. נמצא <code dir=\"ltr\">&lt;$1 $2=\"$3\"&gt;</code>.",
-       "uploaded-href-unsafe-target-svg": "נמצא href לנתונים לא מאובטחים <code dir=\"ltr\">&lt;$1 $2=\"$3\"&gt;</code> בקובץ ה־SVG שהועלה.",
+       "uploaded-href-unsafe-target-svg": "נמצא href לנתונים לא מאובטחים: יעד URI <code dir=\"ltr\">&lt;$1 $2=\"$3\"&gt;</code> בקובץ ה־SVG שהועלה.",
        "uploaded-animate-svg": "נמצא תג \"animate\" שיכול לשנות href באמצעות מאפיין \"from\"  בצורת <code dir=\"ltr\">&lt;$1 $2=\"$3\"&gt;</code> בקובץ ה־SVG שהועלה.",
        "uploaded-setting-event-handler-svg": "הגדרת מאפייני טיפול באירועים חסומה, נמצא <code dir=\"ltr\">&lt;$1 $2=\"$3\"&gt;</code> בקובץ ה־SVG שהועלה.",
-       "uploaded-setting-href-svg": "השימוש בתג set כדי להוסיף מאפיין href לאלמנט הורה חסום.",
+       "uploaded-setting-href-svg": "השימוש בתג \"set\" כדי להוסיף מאפיין \"href\" לאלמנט הורה חסום.",
        "uploaded-wrong-setting-svg": "השימוש בתג \"set\" כדי להוסיף יעד remote/data/script לכל מאפיין חסום. נמצא <code dir=\"ltr\">&lt;set to=\"$1\"&gt;</code> בקובץ ה־SVG שהועלה.",
        "uploaded-setting-handler-svg": "SVG שמגדיר את המאפיין \"handler\" עם remote/data/script חסום. נמצא <code dir=\"ltr\">$1=\"$2\"</code> בקובץ ה־SVG שהועלה.",
        "uploaded-remote-url-svg": "SVG שמגדיר כל מאפיין style עם URL מרוחק חסום. נמצא <code dir=\"ltr\">$1=\"$2\"</code> בקובץ ה־SVG שהועלה.",
        "uploaded-image-filter-svg": "נמצא מסנן תמונה עם URL‏: <code dir=\"ltr\">&lt;$1 $2=\"$3\"&gt;</code> בקובץ ה־SVG שהועלה.",
-       "uploadscriptednamespace": "ק×\95×\91×¥ ×\94â\80\8fâ\80\8fÖ«Ö¾SVG ×\94×\96×\94 ×\9b×\95×\9c×\9c ×\9eר×\97×\91 ×©×\9d ×\91×\9cת×\99 חוקי \"<nowiki>$1</nowiki>\".",
+       "uploadscriptednamespace": "ק×\95×\91×¥ ×\94â\80\8fâ\80\8fÖ¾SVG ×\94×\96×\94 ×\9b×\95×\9c×\9c ×\9eר×\97×\91 ×©×\9d ×\91×\9cת×\99Ö¾חוקי \"<nowiki>$1</nowiki>\".",
        "uploadinvalidxml": "לא ניתן לפרש את ה־XML בקובץ שהועלה.",
        "uploadvirus": "הקובץ מכיל וירוס!\nפרטים:\n<div dir=\"ltr\">$1</div>",
        "uploadjava": "קובץ זה הוא קובץ ZIP שמכיל קובץ &lrm;.class של Java.\nהעלאת קובצי Java אסורה, כיוון שהם יכולים לגרום לעקיפת מגבלות האבטחה.",
        "upload-description": "תיאור הקובץ",
        "upload-options": "אפשרויות העלאה",
        "watchthisupload": "מעקב אחרי קובץ זה",
-       "filewasdeleted": "ק×\95×\91×¥ ×\91ש×\9d ×\96×\94 ×\9b×\91ר ×\94×\95×¢×\9c×\94 ×\91×¢×\91ר, ×\95×\9c×\90×\97ר ×\9e×\9b×\9f × ×\9e×\97ק.\n×\90× ×\90 ×\91Ö´Ö¼×\93ק×\95 ×\90ת $1 ×\9cפנ×\99 ×©×ª×\9eש×\99×\9b×\95 ×\9c×\94×¢×\9c×\95ת את הקובץ שנית.",
+       "filewasdeleted": "ק×\95×\91×¥ ×\91ש×\9d ×\96×\94 ×\9b×\91ר ×\94×\95×¢×\9c×\94 ×\91×¢×\91ר, ×\95×\9c×\90×\97ר ×\9e×\9b×\9f × ×\9e×\97ק.\n×\99ש ×\9c×\91×\93×\95ק ×\90ת $1 ×\9cפנ×\99 ×\94×¢×\9cאת הקובץ שנית.",
        "filename-thumb-name": "נראה שכותרת הקובץ היא כותרת של תמונה מוקטנת (ממוזערת). יש להימנע מהעלאת תמונות ממוזערות בחזרה לאותו אתר ויקי. אם זו אינה תמונה ממוזערת, יש לתקן את שם הקובץ כך שיהיה משמעותי יותר ושלא יכלול את הקידומת של תמונה ממוזערת.",
-       "filename-bad-prefix": "ש×\9d ×\94ק×\95×\91×¥ ×©×\90ת×\9d ×\9e×¢×\9c×\99×\9d ×\9eת×\97×\99×\9c ×\91Ö¾<strong>\"$1\"</strong>, ×©×\94×\95×\90 ×©×\9d ×©×\90×\99× ×\95 ×\9eת×\90ר ×\90ת ×\94ק×\95×\91×¥ ×\95×\91×\93ר×\9a כלל מוקצה אוטומטית על־ידי מצלמות דיגיטליות.\nיש לבחור שם מתאים יותר לקובץ, שיתאר את תכניו.",
+       "filename-bad-prefix": "ש×\9d ×\94ק×\95×\91×¥ ×©×\91×\97רת ×\9c×\94×¢×\9c×\95ת ×\9eת×\97×\99×\9c ×\91Ö¾<strong>\"$1\"</strong>, ×©×\94×\95×\90 ×©×\9d ×©×\90×\99× ×\95 ×\9eת×\90ר ×\90ת ×\94ק×\95×\91×¥ ×\95×\91×\93ר×\9aÖ¾כלל מוקצה אוטומטית על־ידי מצלמות דיגיטליות.\nיש לבחור שם מתאים יותר לקובץ, שיתאר את תכניו.",
        "filename-prefix-blacklist": " #<!-- נא להשאיר שורה זו בדיוק כפי שהיא --> <pre>\n# התחביר הוא כדלקמן:\n#   * כל דבר מתו \"#\" לסוף השורה הוא הערה\n#   * כל שורה לא ריקה היא קידומת לשמות קבצים טיפוסיים שמצלמות דיגיטליות נותנות אוטומטית\nCIMG # Casio\nDSC_ # Nikon\nDSCF # Fuji\nDSCN # Nikon\nDUW # מספר טלפונים סלולריים\nIMG # כללי\nJD # Jenoptik\nMGP # Pentax\nPICT # שונות\n #</pre> <!-- נא להשאיר שורה זו בדיוק כפי שהיא -->",
        "upload-proto-error": "פרוטוקול שגוי",
-       "upload-proto-error-text": "בהעלאה מרוחקת, יש להשתמש בכתובות URL המתחילות עם <code>http://</code> או <code>ftp://</code>.",
+       "upload-proto-error-text": "בהעלאה מרוחקת, יש להשתמש בכתובות URL המתחילות עם <code dir=\"ltr\">http://</code> או עם <code dir=\"ltr\">ftp://</code>.",
        "upload-file-error": "שגיאה פנימית",
-       "upload-file-error-text": "ש×\92×\99×\90×\94 ×¤× ×\99×\9e×\99ת ×\94תר×\97ש×\94 ×\91עת ×\94× ×\99ס×\99×\95×\9f ×\9c×\99צ×\95ר ×§×\95×\91×¥ ×\96×\9e× ×\99 ×¢×\9c ×\94שרת.\n×\90× ×\90 ×¦×¨×\95 קשר עם [[Special:ListUsers/sysop|מפעיל מערכת]].",
+       "upload-file-error-text": "ש×\92×\99×\90×\94 ×¤× ×\99×\9e×\99ת ×\94תר×\97ש×\94 ×\91עת ×\94× ×\99ס×\99×\95×\9f ×\9c×\99צ×\95ר ×§×\95×\91×¥ ×\96×\9e× ×\99 ×¢×\9c ×\94שרת.\n× ×\90 ×\9c×\99צ×\95ר קשר עם [[Special:ListUsers/sysop|מפעיל מערכת]].",
        "upload-misc-error": "שגיאת העלאה בלתי ידועה",
-       "upload-misc-error-text": "שגיאת העלאה בלתי ידועה התרחשה במהלך ההעלאה.\nאנא ודאו שכתובת ה־URL תקינה וזמינה ונסו שוב.\nאם הבעיה חוזרת על עצמה, אנא צרו קשר עם [[Special:ListUsers/sysop|מפעיל מערכת]].",
+       "upload-misc-error-text": "שגיאת העלאה בלתי־ידועה התרחשה במהלך ההעלאה.\nנא לוודא שכתובת ה־URL תקינה וזמינה ולנסות שוב.\nאם הבעיה חוזרת על עצמה, יש ליצור קשר עם [[Special:ListUsers/sysop|מפעיל מערכת]].",
        "upload-too-many-redirects": "הכתובת מכילה הפניות רבות מדי",
        "upload-http-error": "התרחשה שגיאת HTTP‏: $1",
        "upload-copy-upload-invalid-domain": "העלאת קבצים משרת זה אינה אפשרית.",
        "upload-form-label-infoform-categories": "קטגוריות",
        "upload-form-label-infoform-date": "תאריך",
        "upload-form-label-own-work-message-generic-local": "ההעלאה מבוצעת בהתאם לתנאי השירות ולמדיניות הרישיונות ב{{grammar:תחילית|{{SITENAME}}}}.",
-       "upload-form-label-not-own-work-message-generic-local": "אם אין באפשרותך להעלות את הקובץ הזה לפי המדיניות של {{SITENAME}}, עליך לסגור את התיבה הנוכחית ולנסות שיטה אחרת.",
+       "upload-form-label-not-own-work-message-generic-local": "אם אין באפשרותך להעלות את הקובץ הזה לפי המדיניות של {{SITENAME}}, {{GENDER:|עליך|עלייך}} לסגור את התיבה הנוכחית ולנסות שיטה אחרת.",
        "upload-form-label-not-own-work-local-generic-local": "באפשרותך לנסות להשתמש ב[[Special:Upload|דף ברירת המחדל להעלאת קבצים]].",
        "upload-form-label-own-work-message-generic-foreign": "ידוע לי שאני מעלה את הקובץ הזה למאגר משותף. ההעלאה מבוצעת בהתאם לתנאי השירות ולמדיניות הרישיונות שם.",
-       "upload-form-label-not-own-work-message-generic-foreign": "אם אין באפשרותך להעלות את הקובץ הזה לפי המדיניות של המאגר המשותף, עליך לסגור את התיבה הנוכחית ולנסות שיטה אחרת.",
+       "upload-form-label-not-own-work-message-generic-foreign": "אם אין באפשרותך להעלות את הקובץ הזה לפי המדיניות של המאגר המשותף, {{GENDER:|עליך|עלייך}} לסגור את התיבה הנוכחית ולנסות שיטה אחרת.",
        "upload-form-label-not-own-work-local-generic-foreign": "באפשרותך לנסות להשתמש ב[[Special:Upload|דף העלאת הקבצים ב{{grammar:תחילית|{{SITENAME}}}}]], אם ניתן להעלות את הקובץ הזה לשם לפי מדיניות האתר.",
        "backend-fail-stream": "לא הייתה אפשרות להזרים את הקובץ \"$1\".",
        "backend-fail-backup": "לא הייתה אפשרות לגבות את הקובץ \"$1\".",
        "uploadstash": "סליק העלאות",
        "uploadstash-summary": "דף זה מאפשר גישה לקבצים שהועלו (או נמצאים בתהליך העלאה), אך טרם פורסמו באתר הוויקי. קבצים אלה אינם גלויים לאיש מלבד המשתמש שהעלה אותם.",
        "uploadstash-clear": "מחיקת הקבצים בסליק",
-       "uploadstash-nofiles": "×\90×\99×\9f ×\9c×\9b×\9d קבצים בסליק.",
-       "uploadstash-badtoken": "×\91×\99צ×\95×¢ ×\94פע×\95×\9c×\94 × ×\9bש×\9c, ×\90×\95×\9c×\99 ×\91×\92×\9c×\9c ×¤×§×\99עת ×ª×\95קפ×\95 ×©×\9c ×\90ס×\99×\9e×\95×\9f ×\94ער×\99×\9b×\94 ×©×\9c×\9b×\9d. נא לנסות שוב.",
+       "uploadstash-nofiles": "×\90×\99×\9f ×\9c×\9a קבצים בסליק.",
+       "uploadstash-badtoken": "×\91×\99צ×\95×¢ ×\94פע×\95×\9c×\94 × ×\9bש×\9c, ×\90×\95×\9c×\99 ×\91×\92×\9c×\9c ×¤×§×\99עת ×ª×\95קפ×\95 ×©×\9c ×\90ס×\99×\9e×\95×\9f ×\94ער×\99×\9b×\94 ×©×\9c×\9a. נא לנסות שוב.",
        "uploadstash-errclear": "מחיקת הקבצים נכשלה.",
        "uploadstash-refresh": "רענון רשימת הקבצים",
        "uploadstash-thumbnail": "הצגת תמונה ממוזערת",
        "uploadstash-zero-length": "הקובץ באורך אפס.",
        "invalid-chunk-offset": "היסט גוש לא תקין",
        "img-auth-accessdenied": "הגישה נדחתה",
-       "img-auth-nopathinfo": "PATH_INFO ×\97סר.\n×\94שרת ×\90×\99× ×\95 ×\9e×\95×\92×\93ר ×\9c×\94×¢×\91רת ×\9e×\99×\93×¢ ×\96×\94.\n×\99×\99ת×\9b×\9f ×©×\94×\95×\90 ×\9e×\91×\95סס ×¢×\9c CGI ×\95×\9c×\9b×\9f ×\90×\99× ×\95 ×\99×\9b×\95×\9c ×\9cת×\9e×\95×\9a ×\91Ö¾img_auth.\nר×\90×\95 https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
+       "img-auth-nopathinfo": "PATH_INFO ×\97סר.\n×\94שרת ×\90×\99× ×\95 ×\9e×\95×\92×\93ר ×\9c×\94×¢×\91רת ×\9e×\99×\93×¢ ×\96×\94.\n×\99×\99ת×\9b×\9f ×©×\94×\95×\90 ×\9e×\91×\95סס ×¢×\9c CGI ×\95×\9c×\9b×\9f ×\90×\99× ×\95 ×\99×\9b×\95×\9c ×\9cת×\9e×\95×\9a ×\91Ö¾img_auth.\n×\9c×\9e×\99×\93×¢ × ×\95סף: https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
        "img-auth-notindir": "הנתיב המבוקש אינו בתיקיית ההעלאות שהוגדרה.",
        "img-auth-badtitle": "לא ניתן ליצור כותרת תקינה מתוך \"$1\".",
-       "img-auth-nologinnWL": "×\90×\99× ×\9b×\9d ×\9e×\97×\95×\91ר×\99×\9d ×\9c×\97ש×\91×\95×\9f והדף \"$1\" אינו ברשימה המותרת.",
+       "img-auth-nologinnWL": "×\9c×\90 × ×\9bנסת ×\9c×\97ש×\91×\95×\9f, והדף \"$1\" אינו ברשימה המותרת.",
        "img-auth-nofile": "הקובץ \"$1\" אינו קיים.",
-       "img-auth-isdir": "×\90ת×\9d ×\9eנס×\99×\9d לגשת לתיקייה \"$1\".\nרק גישה לקבצים מותרת.",
-       "img-auth-streaming": "×\9e×\91צע הזרמה של \"$1\".",
+       "img-auth-isdir": "× ×\99ס×\99ת לגשת לתיקייה \"$1\".\nרק גישה לקבצים מותרת.",
+       "img-auth-streaming": "×\9eת×\91צעת הזרמה של \"$1\".",
        "img-auth-public": "img_auth.php משמש להצגת קבצים מתוך אתר ויקי פרטי.\nאתר ויקי זה מוגדר כציבורי.\nכדי להשיג אבטחה מרבית, img_auth.php מבוטל.",
        "img-auth-noread": "למשתמש אין הרשאה לקרוא את \"$1\".",
        "http-invalid-url": "כתובת URL בלתי תקינה: $1",
        "http-curl-error": "שגיאה בקבלת כתובת ה־URL‏: $1",
        "http-bad-status": "הייתה בעיה בשליחת בקשת ה־HTTP‏: $1 $2",
        "upload-curl-error6": "לא ניתן להגיע ל־URL",
-       "upload-curl-error6-text": "×\9c×\90 × ×\99ת×\9f ×\9c×\9bת×\95×\91ת ×\94Ö¾URL ×©× ×\9bת×\91×\94. ×\90× ×\90 ×\91×\93ק×\95 אם כתובת זו נכונה ואם האתר זמין.",
+       "upload-curl-error6-text": "×\9c×\90 × ×\99ת×\9f ×\9c×\94×\92×\99×¢ ×\9c×\9bת×\95×\91ת ×\94Ö¾URL ×©× ×\9bת×\91×\94.\n×\99ש ×\9c×\91×\93×\95ק אם כתובת זו נכונה ואם האתר זמין.",
        "upload-curl-error28": "הסתיים זמן ההמתנה להעלאה",
-       "upload-curl-error28-text": "לאתר לקח זמן רב מדי לענות. אנא בדקו שהאתר זמין, המתינו מעט ונסו שוב. ייתכן שתרצו לנסות בזמן פחות עמוס.",
+       "upload-curl-error28-text": "לאתר לקח זמן רב מדי לענות.\nנא לבדוק שהאתר זמין, להמתין מעט ולנסות שוב.\nייתכן שכדאי לנסות בזמן פחות עמוס.",
        "license": "רישיון:",
        "license-header": "רישיון",
-       "nolicense": "×\90×\99×\9f",
+       "nolicense": "×\9c×\90 × ×\91×\97ר",
        "licenses-edit": "עריכת אפשרויות רישיון",
        "license-nopreview": "(תצוגה מקדימה לא זמינה)",
        "upload_source_url": "(קובץ שבחרת מכתובת URL תקינה ונגישה לציבור)",
        "listfiles_size": "גודל",
        "listfiles_description": "תיאור",
        "listfiles_count": "גרסאות",
-       "listfiles-show-all": "×\9b×\95×\9cל גרסאות ישנות של קבצים",
+       "listfiles-show-all": "×\9c×\9b×\9c×\95ל גרסאות ישנות של קבצים",
        "listfiles-latestversion": "גרסה נוכחית",
        "listfiles-latestversion-yes": "כן",
        "listfiles-latestversion-no": "לא",
        "linkstoimage": "{{PLURAL:$1|הדף הבא משתמש|הדפים הבאים משתמשים}} בקובץ זה:",
        "linkstoimage-more": "יותר {{PLURAL:$1|מדף אחד מקשר|מ־$1 דפים מקשרים}} לקובץ זה.\nהרשימה הבאה מראה רק את {{PLURAL:$1|הדף הראשון שמקשר|$1 הדפים הראשונים שמקשרים}} לקובץ זה.\nניתן לצפות ב[[Special:WhatLinksHere/$2|רשימה המלאה]].",
        "nolinkstoimage": "אין דפים המשתמשים בקובץ זה.",
-       "morelinkstoimage": "ר×\90×\95 [[Special:WhatLinksHere/$1|דפים נוספים]] שמשתמשים בקובץ זה.",
+       "morelinkstoimage": "×\99שנ×\9d [[Special:WhatLinksHere/$1|דפים נוספים]] שמשתמשים בקובץ זה.",
        "linkstoimage-redirect": "$1 (הפניה של קובץ) $2",
-       "duplicatesoffile": "{{PLURAL:$1|הקובץ הבא זהה|הקבצים הבאים זהים}} לקובץ זה ([[Special:FileDuplicateSearch/$2|לפרטים נוספים]]):",
+       "duplicatesoffile": "{{PLURAL:$1|הקובץ הבא זהה|$1 הקבצים הבאים זהים}} לקובץ זה ([[Special:FileDuplicateSearch/$2|לפרטים נוספים]]):",
        "sharedupload": "זהו קובץ מתוך $1 וניתן להשתמש בו גם במיזמים אחרים.",
-       "sharedupload-desc-there": "×\96×\94×\95 ×§×\95×\91×¥ ×\9eת×\95×\9a $1 ×\95× ×\99ת×\9f ×\9c×\94שת×\9eש ×\91×\95 ×\92×\9d ×\91×\9e×\99×\96×\9e×\99×\9d ×\90×\97ר×\99×\9d.\n×\9c×\9e×\99×\93×¢ × ×\95סף, ×¨×\90×\95 ×\90ת [$2 דף תיאור הקובץ].",
+       "sharedupload-desc-there": "×\96×\94×\95 ×§×\95×\91×¥ ×\9eת×\95×\9a $1 ×\95× ×\99ת×\9f ×\9c×\94שת×\9eש ×\91×\95 ×\92×\9d ×\91×\9e×\99×\96×\9e×\99×\9d ×\90×\97ר×\99×\9d.\n×\9c×\9e×\99×\93×¢ × ×\95סף, × ×\99ת×\9f ×\9c×¢×\99×\99×\9f ×\91[$2 דף תיאור הקובץ].",
        "sharedupload-desc-here": "זהו קובץ מתוך $1 וניתן להשתמש בו גם במיזמים אחרים.\nתיאורו ב[$2 דף תיאור הקובץ] שלו מוצג למטה.",
        "sharedupload-desc-edit": "זהו קובץ מתוך $1 וניתן להשתמש בו גם במיזמים אחרים.\nניתן לערוך את התקציר שלו ב[$2 דף תיאור הקובץ] שם.",
        "sharedupload-desc-create": "זהו קובץ מתוך $1 וניתן להשתמש בו גם במיזמים אחרים.\nניתן לערוך את התקציר שלו ב[$2 דף תיאור הקובץ] שם.",
        "filepage-nofile-link": "לא קיים קובץ בשם זה, אך באפשרותך [$1 להעלותו].",
        "uploadnewversion-linktext": "העלאת גרסה חדשה של קובץ זה",
        "shared-repo-from": "מתוך $1",
-       "shared-repo": "×\9eק×\95×\9d ×\90×\99×\97ס×\95×\9f ×\9eש×\95תף",
+       "shared-repo": "מקום אחסון משותף",
        "shared-repo-name-wikimediacommons": "ויקישיתוף",
        "filepage.css": "/* הסגנונות הנכתבים כאן יוכללו בדף תיאור הקובץ, כולל באתרי ויקי זרים */",
        "upload-disallowed-here": "אין באפשרותך לדרוס את הקובץ הזה.",
index 74aded1..b76598d 100644 (file)
@@ -37,7 +37,8 @@
                        "Ivi104",
                        "Сербијана",
                        "Wumbolo",
-                       "Fitoschido"
+                       "Fitoschido",
+                       "Hamster"
                ]
        },
        "tog-underline": "Podcrtavanje poveznica",
        "savechanges": "Sačuvaj stranicu",
        "publishpage": "Objavi stranicu",
        "publishchanges": "Sačuvaj uređivanje",
+       "savearticle-start": "Sačuvaj stranicu...",
+       "savechanges-start": "Spremi promjene...",
+       "publishpage-start": "Objavi stranicu...",
+       "publishchanges-start": "Sačuvaj uređivanje...",
        "preview": "Pregled kako će stranica izgledati",
        "showpreview": "Prikaži kako će izgledati",
        "showdiff": "Prikaži promjene",
        "accmailtext": "Nova zaporka za [[User talk:$1|$1]] je poslana na $2.\n\nNakon prijave, zaporka za ovaj novi račun može biti promijenjena na stranici ''[[Special:ChangePassword|promijeni zaporku]]'' nakon prijave.",
        "newarticle": "(Novo)",
        "newarticletext": "Došli ste na stranicu koja još ne postoji.\nAko želite stvoriti tu stranicu, počnite tipkati u prozor ispod ovog teksta (pogledajte [$1 stranicu za pomoć]).\nAko ste ovamo dospjeli slučajno, kliknite gumb '''natrag''' (back) u svom pregledniku.",
-       "anontalkpagetext": "----''Ovo je stranica za razgovor s neprijavljenim suradnikom koji još nije otvorio suradnički račun ili se njime ne koristi. Zbog toga se moramo služiti brojčanom IP adresom kako bismo ga identificirali. Takvu adresu često može dijeliti više ljudi. Ako ste neprijavljeni suradnik i smatrate da su Vam upućeni irelevantni komentari, molimo Vas da [[Special:CreateAccount|otvorite suradnički račun]] ili [[Special:UserLogin|se prijavite]] te tako u budućnosti izbjegnete zamjenu s drugim neprijavljenim suradnicima.''",
+       "anontalkpagetext": "----\n<em>Ovo je stranica za razgovor s neprijavljenim suradnikom koji još nije otvorio suradnički račun ili se njime ne koristi.</em>\nZbog toga se moramo služiti brojčanom IP adresom kako bismo ga identificirali. \nTakvu adresu često može dijeliti više ljudi. \nAko ste neprijavljeni suradnik i smatrate da su Vam upućeni irelevantni komentari, molimo Vas da [[Special:CreateAccount|otvorite suradnički račun]] ili [[Special:UserLogin|se prijavite]] te tako u budućnosti izbjegnete zamjenu s drugim neprijavljenim suradnicima.",
        "noarticletext": "Na ovoj stranici trenutačno nema sadržaja.\nMožete [[Special:Search/{{PAGENAME}}|potražiti ovaj naslov]] na drugim stranicama,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti povezane evidencije]\nili [{{fullurl:{{FULLPAGENAME}}|action=edit}} stvoriti ovu stranicu]</span>.",
        "noarticletext-nopermission": "Ova stranica nema sadržaja.\nMožete [[Special:Search/{{PAGENAME}}|tražiti naslov ove stranice]] na drugim stranicama ili <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} pretražiti povezane evidencije]</span>, ali ne možete stvoriti ovu stranicu.",
        "missing-revision": "Uređivanje broj $1 na stranici \"{{FULLPAGENAME}}\" ne postoji.\n\nOvo je obično uzrokovano kada kliknete na zastarjelu poveznicu na stranice koja je obrisana.\nViše informacija možete pronaći u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} evidenciji brisanja].",
        "filepage-nofile-link": "Ne postoji datoteka s ovim imenom, ali možete je [$1 postaviti].",
        "uploadnewversion-linktext": "Postavi novu inačicu datoteke",
        "shared-repo-from": "s projekta $1",
-       "shared-repo": "zajednički poslužitelj",
+       "shared-repo": "Zajednički poslužitelj",
        "shared-repo-name-wikimediacommons": "Zajednički poslužitelj",
        "upload-disallowed-here": "Ne možete prepisati ovu datoteku.",
        "filerevert": "Ukloni ← $1",
        "special-characters-group-thai": "tajlandski (tajski)",
        "special-characters-group-lao": "laoski",
        "special-characters-group-khmer": "kmerski",
-       "special-characters-group-canadianaboriginal": "Kanadski domorodni",
+       "special-characters-group-canadianaboriginal": "kanadski domorodni",
        "special-characters-title-endash": "crtica",
        "special-characters-title-emdash": "dulja crtica",
        "special-characters-title-minus": "znak za minus",
index 0f0e74d..652f58e 100644 (file)
@@ -45,7 +45,7 @@
        "tog-shownumberswatching": "Ličbu wobkedźbowacych wužiwarjow pokazać",
        "tog-oldsig": "Twoja eksistowaca signatura:",
        "tog-fancysig": "Ze signaturu kaž z wikitekstom wobchadźeć  (bjez awtomatiskeho wotkaza)",
-       "tog-uselivepreview": "Live-přehlad wužiwać",
+       "tog-uselivepreview": "Přehlad pokazać, bjeztoho zo by so strona znowa začitała",
        "tog-forceeditsummary": "Mje skedźbnić, jeli zabudu zjeće",
        "tog-watchlisthideown": "Moje změny we wobkedźbowankach schować",
        "tog-watchlisthidebots": "Změny awtomatiskich programow (botow) we wobkedźbowankach schować",
        "nosuchusershort": "Wužiwarske mjeno „$1” njeeksistuje. Prošu skontroluj prawopis.",
        "nouserspecified": "Dyrbiš wužiwarske mjeno podać",
        "login-userblocked": "Tutón wužiwar je zablokowany. Přizjewjenje njedowolene.",
-       "wrongpassword": "Hesło, kotrež sy zapodał, je wopačne. Prošu spytaj hišće raz.",
+       "wrongpassword": "Wužiwarske mjeno abo hesło, kotrež sy zapodał, je wopačne. Prošu spytaj hišće raz.",
        "wrongpasswordempty": "Hesło, kotrež sy zapodał, běše prózdne. Prošu spytaj hišće raz.",
        "passwordtooshort": "Hesła dyrbja znajmjeńša {{PLURAL:$1|1 znamješko|$1 znamješce|$1 znamješka|$1 znamješkow}} měć.",
        "passwordtoolong": "Hesła njesmědźa dlěše jako {{PLURAL:$1|1 znamješko|$1 znamješce|$1 znamješka|$1 znamješkow}} być.",
        "permissionserrorstext": "Nimaš prawo, zo by tutu akciju wuwjedł. {{PLURAL:$1|Přičina|Přičiny}}:",
        "permissionserrorstext-withaction": "Nimaš prawo $2. {{PLURAL:$1|Přičina|Přičinje|Přičiny|Přičiny}}:",
        "recreate-moveddeleted-warn": "'''Kedźbu: Wutworiš stronu, kiž bu prjedy wušmórnjena.'''\n\nProšu přepruwuj, hač je přihódne z wobdźěłowanjom tuteje strony pokročować.\nProtokol wušmórnjenjow a přesunjenjow za tutu stronu su tu za informaciju:",
-       "moveddeleted-notice": "Tuta strona bu wušmórnjena. Protokol wušmórnjenjow a přesunjenjow za  stronu so deleka jako referenca podawa.",
+       "moveddeleted-notice": "Tuta strona bu wušmórnjena.\nProtokol wušmórnjenjow, přesunjenjow a škit strony so deleka jako referenca podawa.",
        "log-fulllog": "Dospołny protokol sej wobhladać",
        "edit-hook-aborted": "Wobdźěłanje přez hoku přetorhnjene.\nNjeje žane wujasnjenje podała.",
        "edit-gone-missing": "Strona njeje so aktualizować dała.\nZda so, zo je hîžo wušmórnjena.",
        "page_first": "spočatk",
        "page_last": "kónc",
        "histlegend": "Diff wubrać: Wubjer opciske pola za přirunanje a tłóč na enter abo tłóčku deleka.\n\nLegenda: (akt) = rozdźěl k tuchwilnej wersiji, (posl) = rozdźěl k předchadnej wersiji, S = snadna změna.",
-       "history-fieldset-title": "Stawizny přepytać",
+       "history-fieldset-title": "Wersije pytać",
        "history-show-deleted": "Jenož wušmórnjene",
        "histfirst": "najstaršu",
        "histlast": "najnowšu",
        "searchprofile-advanced-tooltip": "W swójskich mjenowych rumach pytać",
        "search-result-size": "$1 ({{PLURAL:$2|1 słowo|$2 słowje|$2 słowa|$2 słowow}})",
        "search-result-category-size": "{{PLURAL:$1|1 čłon|$1 čłonaj|$1 čłonojo|$1 čłonow}} ({{PLURAL:$2|1 podkategorija|$2 podkategoriji|$2 podkategorije|$2 podkategorijow}}, {{PLURAL:$3|1 dataja|$3 dataji|$3 dataje|$3 datajow}})",
-       "search-redirect": "(Daleposrědkowanje $1)",
+       "search-redirect": "(daleposrědkowanje wot $1)",
        "search-section": "(wotrězk $1)",
        "search-category": "(kategorija $1)",
        "search-file-match": "(wotpowěduje datajowemu wobsahej)",
        "rcshowhidemine-hide": "schować",
        "rcshowhidecategorization-show": "Pokazać",
        "rcshowhidecategorization-hide": "Schować",
-       "rclinks": "Pokazuj poslednje $1 změny poslednich $2 dnjow.",
+       "rclinks": "Poslednje $1 změnow poslednich $2 dnjow pokazać",
        "diff": "rozdźěl",
        "hist": "wersije",
        "hide": "schować",
        "recentchangeslinked-feed": "Změny zwjazanych stron",
        "recentchangeslinked-toolbox": "Změny na zwjazanych stronach",
        "recentchangeslinked-title": "Změny na stronach, kotrež su z „$1“ wotkazane",
-       "recentchangeslinked-summary": "Tuta strona nalistuje poslednje změny na wotkazanych stronach (resp. pola kategorijow na čłonach kategorije).\nStrony na [[Special:Watchlist|wobkedźbowankach]] su '''tučne'''.",
+       "recentchangeslinked-summary": "Zapodajće mjeno strony, zo byšće změny na stronach widźał, kotrež na tutu stronu abo wot tuteje strony wotkazuja (zo byšće čłonow kategorije widźał, zapodajće Kategorija:\"Mjeno kategorije\").\nZměny na stronach na [[Special:Watchlist|wobkedźbowankach]] su <strong>tučne</strong>.",
        "recentchangeslinked-page": "Mjeno strony:",
        "recentchangeslinked-to": "Změny na stronach pokazać, kotrež na datu stronu wotkazuja",
        "upload": "Dataju nahrać",
        "unwatchthispage": "wobkedźbowanje skónčić",
        "notanarticle": "njeje nastawk",
        "notvisiblerev": "Wersija bu wušmórnjena",
-       "watchlist-details": "{{PLURAL:$1|$1 wobkedźbowana strona|$1 wobkedźbowanej stronje|$1 wobkedźbowane strony|$1 wobkedźbowanych stronow}}, bjeztoho zo so diskusijne strony dźělene liča.",
+       "watchlist-details": "{{PLURAL:$1|$1 strona je|$1 stronje stej|$1 strony su|$1 stronow je}} we wobkedźbowankach (a diskusijnych stronach).",
        "wlheader-enotif": "E-mejlowa zdźělenska słužba je zmóžnjena.",
        "wlheader-showupdated": "Strony, kotrež su so po twojim poslednim wopyće změnili, so '''tučne''' pokazuja.",
        "wlnote": "Deleka {{PLURAL:$1|je poslednja změna|stej poslednjej <strong>$1</strong> změnje|su poslednje <strong>$1</strong> změny|je poslednich <strong>$1</strong> změnow}} za {{PLURAL:$2|poslednju hodźinu|poslednje <strong>$2</strong> hodźinje|poslednje <strong>$2</strong> hodźiny|poslednich <strong>$2</strong> hodźin}}, staw : $3, $4.",
        "version-libraries-library": "Biblioteka",
        "version-libraries-version": "Wersija",
        "redirect": "Na dataju, wužiwarja, stronu abo wersiju abo protokolowy ID dale sposrědkować",
-       "redirect-summary": "Tuta specialna strona so do dataje (datajowe mjeno je podate), strony (wersijowy ID abo ID strony je podaty) abo wužiwarskeje strony (numeriski wužiwarski ID je podaty) dale sposrědkuje. Wužiće:\n[[{{#Special:Redirect}}/file/Přikład.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]] abo [[{{#Special:Redirect}}/user/101]].",
+       "redirect-summary": "Tuta specialna strona so do dataje (datajowe mjeno je podate), strony (wersijowy ID abo ID strony je podaty), wužiwarskeje strony (numeriski wužiwarski ID je podaty) abo protokoloweho zapiska (protokolowy ID je podaty) dale sposrědkuje. Wužiće:\n[[{{#Special:Redirect}}/file/Přikład.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]] abo [[{{#Special:Redirect}}/user/101]].",
        "redirect-submit": "Los",
        "redirect-lookup": "Pytać:",
        "redirect-value": "Hódnota:",
        "htmlform-cloner-delete": "Wotstronić",
        "htmlform-cloner-required": "Znjamjeńša jedna hódnota je trěbna.",
        "logentry-delete-delete": "$1 je stronu $3 {{GENDER:$1|zhašał|zhašała}}",
-       "logentry-delete-restore": "$1 je stronu $3 {{GENDER:$1wobnowił|wobnowiła}}",
+       "logentry-delete-restore": "$1 je stronu $3 ($4) {{GENDER:$2|wobnowił|wobnowiła}}",
        "logentry-delete-event": "$1 je widźomnosć {{PLURAL:$5|protokoloweho zapiska|$5 protokoloweju zapiskow|$5 protokolowych zapiskow}} na $3 {{GENDER:$2|změnił|změniła}}: $4",
        "logentry-delete-revision": "$1 je widźomnosć {{PLURAL:$5|jedneje wersije|$5 wersijow}} na $3 {{GENDER:$2|změnił|změniła}}: $4",
        "logentry-delete-event-legacy": "$1 je widźomnosć protokolowych zapiskow na $3 {{GENDER:$2|změnił|změniła}}",
        "feedback-thanks": "Dźakujemy so! Twój komentar je so k stronje \"[$2 $1]\" pósłał.",
        "feedback-thanks-title": "Wulki dźak!",
        "feedback-useragent": "Identifikator wobhladowaka:",
-       "searchsuggest-search": "Pytać",
+       "searchsuggest-search": "{{GRAMMAR:akuzatiw|{{SITENAME}}}} přepytać",
        "searchsuggest-containing": "wobsahuje...",
        "api-error-badtoken": "Nutřkowny zmylk: Wopačny token.",
        "api-error-emptypage": "Wutworjenje nowych, prózdnych stronow njeje dowolene.",
index 196e1bd..11eb318 100644 (file)
        "default": "alapértelmezett",
        "prefs-files": "Fájlok",
        "prefs-custom-css": "saját CSS",
+       "prefs-custom-json": "saját JSON",
        "prefs-custom-js": "saját JS",
        "prefs-common-config": "Közös CSS/JSON/JS az összes felület számára:",
        "prefs-reset-intro": "Ezen a lapon állíthatod vissza a beállításaidat az oldal alapértelmezett értékeire.\nA műveletet nem lehet visszavonni.",
        "prefs-dateformat": "Dátumformátum",
        "prefs-timeoffset": "Időeltérés",
        "prefs-advancedediting": "Általános",
+       "prefs-developertools": "Fejlesztői eszközök",
        "prefs-editor": "Szerkesztő",
        "prefs-preview": "Előnézet",
        "prefs-advancedrc": "Haladó beállítások",
        "fix-double-redirects": "Az eredeti címre mutató hivatkozások frissítése",
        "move-leave-redirect": "Átirányítás készítése a régi címről az új címre",
        "protectedpagemovewarning": "'''Figyelem:''' Ez a lap le van védve, így csak adminisztrátori jogosultságokkal rendelkező szerkesztők nevezhetik át.\nA legutolsó ide vonatkozó naplóbejegyzés alább látható:",
-       "semiprotectedpagemovewarning": "'''Figyelem:''' Ez a lap le van védve, így csak regisztrált felhasználók nevezhetik át.\nA legutolsó ide vonatkozó naplóbejegyzés alább látható:",
+       "semiprotectedpagemovewarning": "<strong>Figyelem:</strong> Ez a lap le van védve, így csak regisztrált felhasználók nevezhetik át.\nA legutolsó ide vonatkozó naplóbejegyzés alább látható:",
        "move-over-sharedrepo": "A(z) [[:$1]] néven már létezik fájl egy megosztott tárhelyen. Ha ilyen néven töltöd fel, el fogja takarni a közös tárhelyen levőt.",
        "file-exists-sharedrepo": "A választott fájlnév már használatban van egy közös tárhelyen.\nKérlek válassz másik nevet.",
        "export": "Lapok exportálása",
index f0272b2..19c20ab 100644 (file)
@@ -31,7 +31,8 @@
                        "Irus",
                        "Narek",
                        "23artashes",
-                       "Fitoschido"
+                       "Fitoschido",
+                       "Սահակ"
                ]
        },
        "tog-underline": "ընդգծել հղումները՝",
        "copyrightpage": "{{ns:project}}:Հեղինակային իրավունքներ",
        "currentevents": "Ընթացիկ իրադարձություններ",
        "currentevents-url": "Project:Ընթացիկ իրադարձություններ",
-       "disclaimers": "Ազատում պատասխանատվությունից",
+       "disclaimers": "Հրաժարագրեր",
        "disclaimerpage": "Project:Ազատում պատասխանատվությունից",
        "edithelp": "Խմբագրման ուղեցույց",
        "helppage-top-gethelp": "Օգնություն",
        "savechanges": "Պահպանել փոփոխությունները",
        "publishpage": "Ստեղծել էջը",
        "publishchanges": "Հիշել փոփոխությունները",
+       "publishchanges-start": "Հիշել փոփոխությունները…",
        "preview": "Նախադիտում",
        "showpreview": "Նախադիտել",
        "showdiff": "Կատարված փոփոխությունները",
        "recentchangeslinked-page": "Էջի անվանումը՝",
        "recentchangeslinked-to": "Հակառա՛կը. ցույց տալ այս էջին հղող էջերի փոփոխությունները։",
        "recentchanges-page-added-to-category": "[[:$1]] էջը ավելացվել է կատեգորիայում",
-       "upload": "Բեռնել նիշք (ֆայլ)",
+       "upload": "Բեռնել նիշք",
        "uploadbtn": "Բեռնել նիշք",
        "reuploaddesc": "Վերադառնալ բեռնման ձևին։",
        "uploadnologin": "Դուք չեք մտել համակարգ",
        "rollback": "Հետ գլորել խմբագրումները",
        "rollbacklink": "հետ գլորել",
        "rollbacklinkcount": "հետ գլորել $1 {{PLURAL:$1|խմբագրում}}",
+       "rollbacklinkcount-morethan": "հետ գլորել ավելի քան $1 {{PLURAL:$1|խմբագրում|խմբագրում}}",
        "rollbackfailed": "Հետ գլորումը ձախողվեց",
        "cantrollback": "Չհաջողվեց հետ շրջել խմբագրումը։ Վերջին ներդրումը կատարվել է էջի միակ հեղինակի կողմից։",
        "alreadyrolled": "Չհաջողվեց հետ գլորել [[:$1]] էջում [[User:$2|$2]] ([[User talk:$2|Քննարկում]]) մասնակցի վերջին խմբագրումները․ մեկ ուրիշն արդեն հետ է գլորել կամ խմբագրել է էջը։\n\nՎերջին խմբագրումը կատարել է [[User:$3|$3]] ([[User talk:$3|Քննարկում]]) մասնակիցը։",
index a006959..44a87f1 100644 (file)
@@ -96,7 +96,7 @@
        "tog-watchlisthideminor": "Sembunyikan suntingan kecil di daftar pantauan",
        "tog-watchlisthideliu": "Sembunyikan suntingan pengguna masuk log di daftar pantauan",
        "tog-watchlistreloadautomatically": "Muat ulang daftar pantauan secara otomatis ketika sebuah tapis berubah (JavaScript diperlukan)",
-       "tog-watchlistunwatchlinks": "Tambahkan pranala pantau/hapus pantauan ke entri daftar pantauan (JavaScript diperlukan untuk mengganti fungsi ini)",
+       "tog-watchlistunwatchlinks": "Tambahkan penanda pantau/hapus pantauan ke halaman yang dipantau yang berubah (JavaScript diperlukan untuk mengganti fungsi ini)",
        "tog-watchlisthideanons": "Sembunyikan suntingan pengguna anonim di daftar pantauan",
        "tog-watchlisthidepatrolled": "Sembunyikan suntingan terpatroli di daftar pantauan",
        "tog-watchlisthidecategorization": "Sembunyikan pengategorian halaman",
        "cascadeprotected": "Halaman ini telah dilindungi dari penyuntingan karena disertakan di {{PLURAL:$1|halaman|halaman-halaman}} berikut yang telah dilindungi dengan opsi \"runtun\":\n$2",
        "namespaceprotected": "Anda tak memiliki hak akses untuk menyunting halaman di ruang nama '''$1'''.",
        "customcssprotected": "Anda tidak memiliki izin untuk menyunting halaman CSS ini, karena berisi pengaturan pribadi pengguna lain.",
+       "customjsonprotected": "Anda tidak memiliki izin untuk menyunting halaman JSON ini karena berisi pengaturan pribadi pengguna lain.",
        "customjsprotected": "Anda tidak memiliki izin untuk menyunting halaman JavaScript ini, karena berisi pengaturan pribadi pengguna lain.",
        "mycustomcssprotected": "Anda tidak memiliki izin untuk menyunting halaman CSS ini.",
+       "mycustomjsonprotected": "Anda tidak memiliki izin untuk menyunting halaman JSON ini.",
        "mycustomjsprotected": "Anda tidak memiliki izin untuk menyunting halaman JavaScript ini.",
        "myprivateinfoprotected": "Anda tidak memiliki izin untuk menyunting informasi pribadi Anda.",
        "mypreferencesprotected": "Anda tidak memiliki izin untuk menyunting preferensi Anda.",
        "wrongpasswordempty": "Anda tidak memasukkan kata sandi. Silakan coba lagi.",
        "passwordtooshort": "Kata sandi paling tidak harus terdiri dari {{PLURAL:$1|1 karakter|$1 karakter}}.",
        "passwordtoolong": "Passwords tidak boleh lebih dari {{PLURAL:$1|1 karakter|$1 karakter}}.",
-       "passwordtoopopular": "Kata sandi yang umum tidak dapat digunakan. Silakan pilih kata sandi yang berbeda.",
+       "passwordtoopopular": "Kata sandi yang umum tidak dapat digunakan. Silakan pilih kata sandi yang lebih sukar diterka.",
        "password-name-match": "Kata sandi Anda harus berbeda dari nama pengguna Anda.",
        "password-login-forbidden": "Penggunaan nama pengguna dan sandi ini telah dilarang.",
        "mailmypassword": "Setel ulang kata sandi",
        "savechanges": "Simpan perubahan",
        "publishpage": "Terbitkan halaman",
        "publishchanges": "Terbitkan perubahan",
+       "savearticle-start": "Simpan halaman...",
+       "savechanges-start": "Simpan perubahan...",
+       "publishpage-start": "Terbitkan halaman...",
+       "publishchanges-start": "Terbitkan perubahan...",
        "preview": "Pratayang",
        "showpreview": "Lihat pratayang",
        "showdiff": "Lihat perubahan",
        "postedit-confirmation-created": "Halaman telah dibuat.",
        "postedit-confirmation-restored": "Halaman telah dipulihkan.",
        "postedit-confirmation-saved": "Suntingan Anda tersimpan.",
+       "postedit-confirmation-published": "Suntingan Anda diterbitkan.",
        "edit-already-exists": "Tidak dapat membuat halaman baru\nkarena telah ada.",
        "defaultmessagetext": "Teks baku",
        "content-failed-to-parse": "Gagal menjabarkan konten $2 untuk model $1: $3",
        "prefs-dateformat": "Format tanggal",
        "prefs-timeoffset": "Format waktu",
        "prefs-advancedediting": "Pilihan umum",
+       "prefs-developertools": "Alat Pengembang",
        "prefs-editor": "Penyunting",
        "prefs-preview": "Pratayang",
        "prefs-advancedrc": "Opsi lanjutan",
        "right-editusercss": "Menyunting berkas CSS pengguna lain",
        "right-edituserjs": "Menyunting berkas JS pengguna lain",
        "right-editmyusercss": "Sunting berkas CSS pengguna Anda",
+       "right-editmyuserjson": "Sunting berkas JSON pengguna Anda",
        "right-editmyuserjs": "Sunting berkas JavaScript pengguna Anda",
        "right-viewmywatchlist": "Lihat daftar pantauan Anda",
        "right-editmywatchlist": "Sunting daftar pantau Anda. Masih ada cara menambahkan halaman tanpa harus memiliki hak ini.",
        "grant-createaccount": "Buat akun",
        "grant-createeditmovepage": "Membuat, menyunting dan memindahkan halaman",
        "grant-delete": "Menghapus halaman, revisi, dan log entri",
-       "grant-editinterface": "Menyunting ruang nama MediaWiki dan CSS/JavaScript pengguna",
-       "grant-editmycssjs": "Menyunting halaman CSS/JavaScript Anda",
+       "grant-editinterface": "Menyunting ruang nama MediaWiki dan CSS/JSON/JavaScript pengguna",
+       "grant-editmycssjs": "Menyunting halaman CSS/JSON/JavaScript Anda",
        "grant-editmyoptions": "Menyunting preferensi pengguna Anda",
        "grant-editmywatchlist": "Menyunting daftar pantauan Anda",
        "grant-editpage": "Menyunting halaman yang ada",
        "rcfilters-activefilters": "Filter aktif",
        "rcfilters-advancedfilters": "Penyaringan lebih lanjut",
        "rcfilters-limit-title": "Hasil untuk ditampilkan",
+       "rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|perubahan|perubahan}}, $2",
        "rcfilters-date-popup-title": "Periode waktu untuk dicari",
        "rcfilters-days-title": "Hari-hari terakhir",
        "rcfilters-hours-title": "Jam-jam terakhir",
        "rcfilters-filter-humans-label": "Manusia (bukan bot)",
        "rcfilters-filter-humans-description": "Suntingan yang dibuat oleh penyunting manusia.",
        "rcfilters-filtergroup-reviewstatus": "Status peninjauan",
+       "rcfilters-filter-reviewstatus-unpatrolled-description": "Suntingan yang tidak ditandai terpatroli, baik secara manual atau otomatis.",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "Belum terpatroli",
+       "rcfilters-filter-reviewstatus-manual-description": "Suntingan yang secara manual ditandai terpatroli",
+       "rcfilters-filter-reviewstatus-manual-label": "Terpatroli manual",
+       "rcfilters-filter-reviewstatus-auto-label": "Otomatis terpatroli",
        "rcfilters-filtergroup-significance": "Kepentingan",
        "rcfilters-filter-minor-label": "Suntingan kecil",
        "rcfilters-filter-minor-description": "Suntingan yang ditandai penyunting sebagai suntingan kecil",
        "recentchangeslinked-feed": "Perubahan terkait",
        "recentchangeslinked-toolbox": "Perubahan terkait",
        "recentchangeslinked-title": "Perubahan yang terkait dengan \"$1\"",
-       "recentchangeslinked-summary": "Ini adalah daftar perubahan pada halaman yang terkait ke halaman tertentu (atau bagian dari kategori tertentu).\nHalaman pada [[Special:Watchlist|daftar pantauan Anda]] terlihat <strong>dicetak tebal</strong>.",
+       "recentchangeslinked-summary": "Masukkan nama halaman untuk melihat perubahan pada halaman terkait (untuk melihat anggota sebuah kategori, masukkan Kategori:Nama kategori). Perubahan pada [[Special:Watchlist|daftar pantauan Anda]] terlihat <strong>dicetak tebal</strong>.",
        "recentchangeslinked-page": "Nama halaman:",
        "recentchangeslinked-to": "Perlihatkan perubahan dari halaman-halaman yang terhubung dengan halaman yang disajikan",
        "recentchanges-page-added-to-category": "[[:$1]] ditambahkan pada kategori",
        "deadendpages": "Halaman buntu",
        "deadendpagestext": "Halaman-halaman berikut tidak memiliki pranala ke halaman mana pun di wiki ini.",
        "protectedpages": "Halaman yang dilindungi",
+       "protectedpages-filters": "Tapis:",
        "protectedpages-indef": "Hanya untuk pelindungan dengan jangka waktu tak terbatas",
        "protectedpages-summary": "Halaman ini mendaftarkan halaman-halaman yang telah ada yang sedang dilindungi. Untuk daftar judul yang dilindungi dari pembuatan, lihat [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]].",
        "protectedpages-cascade": "Hanya pelindungan runtun",
        "apisandbox-dynamic-error-exists": "Parameter bernama \"$1\" telah tersedia.",
        "apisandbox-deprecated-parameters": "Parameter usang",
        "apisandbox-fetch-token": "Isi token dengan otomatis",
+       "apisandbox-add-multi": "Tambahkan",
        "apisandbox-submit-invalid-fields-title": "Beberapa kolom tidak valid",
        "apisandbox-submit-invalid-fields-message": "Silakan perbaiki kolom yang ditandai dan coba kembali.",
        "apisandbox-results": "Hasil",
        "thumbnail_dest_directory": "Direktori tujuan tak dapat dibuat",
        "thumbnail_image-type": "Tipe gambar tidak didukung",
        "thumbnail_gd-library": "Konfigurasi pustaka GD tak lengkap: tak ada fungsi $1",
+       "thumbnail_image-size-zero": "Ukuran gambar nol.",
        "thumbnail_image-missing": "Berkas yang tampaknya hilang: $1",
        "thumbnail_image-failure-limit": "Ada terlalu banyak upaya yang gagal baru-baru ini ($1 atau lebih) untuk membuat miniatur ini. Silakan coba lagi nanti.",
        "import": "Impor halaman",
        "import-mapping-namespace": "Impor ke ruang nama:",
        "import-mapping-subpage": "Impor sebagai subhalaman dari halaman berikut:",
        "import-upload-filename": "Nama berkas:",
+       "import-upload-username-prefix": "Awalan interwiki:",
        "import-comment": "Komentar:",
        "importtext": "Silakan ekspor berkas dari wiki sumber dengan menggunakan [[Special:Export|fasilitas ekspor]].\nSimpan ke komputer Anda dan unggah ke sini.",
        "importstart": "Mengimpor halaman...",
        "autosumm-blank": "←Mengosongkan halaman",
        "autosumm-replace": "←Mengganti halaman dengan '$1'",
        "autoredircomment": "←Mengalihkan ke [[$1]]",
+       "autosumm-removed-redirect": "Menghapus pengalihan ke [[$1]]",
        "autosumm-new": "←Membuat halaman berisi '$1'",
        "autosumm-newblank": "Membuat halaman kosong",
        "lag-warn-normal": "Perubahan yang lebih baru dari $1 {{PLURAL:$1|detik|detik}} mungkin tidak muncul di daftar ini.",
        "version-specialpages": "Halaman istimewa",
        "version-parserhooks": "Kait parser",
        "version-variables": "Variabel",
+       "version-editors": "Penyunting",
        "version-antispam": "Pencegahan spam",
        "version-api": "API",
        "version-other": "Lain-lain",
index d739147..57f9712 100644 (file)
@@ -6,7 +6,7 @@
                        "아라"
                ]
        },
-       "tog-underline": "Okpürụ ahiri jikodo:",
+       "tog-underline": "Ahịrịàlà òjikọ:",
        "tog-hideminor": "Zonari orü ntàkírí na nwerue mẹrẹ ogẹ nsó",
        "tog-hidepatrolled": "Zonari orü ha hụrụ na nwerue mẹrẹ ogẹ nsó",
        "tog-newpageshidepatrolled": "Zonari orü ha hụrụ shí ndetu ihü ohúrù",
        "tog-diffonly": "É zìkwàlà ihe nọr na ihü di okpúrù íchiè",
        "tog-showhiddencats": "Zi ébéonọr zonari a zonari",
        "tog-norollbackdiff": "Kwà diff mgbe byárá na mgbe láázú mèchàrà",
-       "underline-always": "Ngbẹowula",
-       "underline-never": "Anáobulạ",
+       "underline-always": "M̀gbèọbụlà",
+       "underline-never": "Emelaème",
        "underline-default": "Ndatụ ihü njikota",
        "editfont-style": "Rüwa ámá udị mkpúrù èdè:",
        "editfont-monospace": "Otụ ihe ná kechí mkpúrù èdè",
        "editfont-sansserif": "Mkpúrù èdè sans-serif",
        "editfont-serif": "Mkpúrù èdè Serif",
-       "sunday": "Sondè",
-       "monday": "Mondè",
-       "tuesday": "Tusdè",
-       "wednesday": "Wensdè",
-       "thursday": "Torsdè",
-       "friday": "Fridè",
-       "saturday": "Satorde",
-       "sun": "Son",
-       "mon": "Mon",
-       "tue": "Tus",
-       "wed": "Wen",
-       "thu": "Tor",
-       "fri": "Fri",
-       "sat": "Sat",
-       "january": "Önwa Mbú",
-       "february": "Önwa Abụọ",
-       "march": "Önwa Atọ",
-       "april": "Önwa Anọ",
-       "may_long": "Önwa Ise",
-       "june": "Önwa Isii",
-       "july": "Önwa Asaa",
-       "august": "Önwa Asáto",
-       "september": "Önwa Itolu",
-       "october": "Önwa Iri",
-       "november": "Önwa Iri na otu",
-       "december": "Önwa Iri na abụọ",
-       "january-gen": "Önwa Mbú",
-       "february-gen": "Önwa Abụọ",
-       "march-gen": "Önwa Atọ",
-       "april-gen": "Önwa Anọ",
-       "may-gen": "Önwa Ise",
-       "june-gen": "Önwa Isii",
-       "july-gen": "Önwa Asaa",
-       "august-gen": "Önwa Asatọ",
-       "september-gen": "Önwa Itolu",
-       "october-gen": "Önwa Iri",
-       "november-gen": "Önwa Iri na otu",
-       "december-gen": "Önwa Iri na abụọ",
-       "jan": "ÖMbú",
-       "feb": "ÖAbụ",
-       "mar": "ÖAtọ",
-       "apr": "ÖAnọ",
-       "may": "ÖIse",
-       "jun": "ÖIsi",
-       "jul": "ÖAsa",
-       "aug": "ÖAsọ",
-       "sep": "ÖIto",
-       "oct": "ÖIri",
-       "nov": "ÖIrinotu",
-       "dec": "ÖIrinabụọ",
-       "pagecategories": "{{PLURAL:$1|Ébéonọr|Ébéonọr}}",
+       "sunday": "Èhìchim̀bụ",
+       "monday": "Èhìchiàbụọ",
+       "tuesday": "Èhìchiàtọ",
+       "wednesday": "Èhìchiànọ",
+       "thursday": "Èhìchiìse",
+       "friday": "Èhìchiìsiì",
+       "saturday": "Èhìchiọdụ̀",
+       "sun": "Chi1",
+       "mon": "Chi2",
+       "tue": "Chi3",
+       "wed": "Chi4",
+       "thu": "Chi5",
+       "fri": "Chi6",
+       "sat": "Chi7",
+       "january": "Ọnwam̀bụ",
+       "february": "Ọnwaàbụọ",
+       "march": "Ọnwaàtọ",
+       "april": "Ọnwaànọ",
+       "may_long": "Ọnwaìse",
+       "june": "Ọnwaìsiì",
+       "july": "Ọnwaàsaà",
+       "august": "Ọnwaàsatọ",
+       "september": "Ọnwaìtoolu",
+       "october": "Ọnwaìri",
+       "november": "Ọnwaìrinàotù",
+       "december": "Ọnwaìrinààbụọ",
+       "january-gen": "Ọnwam̀bụ",
+       "february-gen": "Ọnwaàbụọ",
+       "march-gen": "Ọnwaàtọ",
+       "april-gen": "Ọnwaànọ",
+       "may-gen": "Ọnwaìse",
+       "june-gen": "Ọnwaìsiì",
+       "july-gen": "Ọnwaàsaà",
+       "august-gen": "Ọnwaàsatọ",
+       "september-gen": "Ọnwaìtoolu",
+       "october-gen": "Ọnwaìri",
+       "november-gen": "Ọnwaìrinàotù",
+       "december-gen": "Ọnwaìrinààbụọ",
+       "jan": "ỌM̀bụ",
+       "feb": "ỌÀbụ",
+       "mar": "ỌÀtọ",
+       "apr": "ỌÀnọ",
+       "may": "ỌÌse",
+       "jun": "ỌÌsi",
+       "jul": "ỌÀsa",
+       "aug": "ỌÀst",
+       "sep": "ỌÌto",
+       "oct": "ỌÌri",
+       "nov": "Ọn11",
+       "dec": "Ọn12",
+       "january-date": "Ọnwam̀bụ $1",
+       "february-date": "Ọnwaàbụọ $1",
+       "march-date": "Ọnwaàtọ $1",
+       "april-date": "Ọnwaànọ $1",
+       "may-date": "Ọnwaìse $1",
+       "june-date": "Ọnwaìsiì $1",
+       "july-date": "Ọnwaàsaà $1",
+       "august-date": "Ọnwaàsatọ $1",
+       "september-date": "Ọnwaìtoolu $1",
+       "october-date": "Ọnwaìri $1",
+       "november-date": "Ọnwaìrinàotù $1",
+       "december-date": "Ọnwaìrinààbụọ",
+       "pagecategories": "{{PLURAL:$1|Ụdàkọ}}",
        "category_header": "Ihü nọr ime ébéonọr \"$1\"",
-       "subcategories": "Ébéonọr ime ime",
+       "subcategories": "Ụdàkọòkpurù",
        "category-media-header": "Nka nọr ime ébéonọr \"$1\"",
        "category-empty": "\"Ébéonọr nke enwéghị ihü ma nkà ímé ya.\"",
-       "hidden-categories": "{{PLURAL:$1|Ébéonọr zonari|Ébéonọr zonari}}",
+       "hidden-categories": "{{PLURAL:$1|Ụdàkọ nzezò}}",
        "hidden-category-category": "Ébéanọr zonari a zonari",
        "category-subcat-count": "{{PLURAL:$2|Ébéanọr nka nwerechạ ébéanọr-ime nkeá.|Ébéanọr nka nwere {{PLURAL:$1|ébéanọr-ime|$1 ébéanọr-ime}}, guru nke $2 total.}}",
        "category-subcat-count-limited": "Ébéonọr nke á nwèrè {{PLURAL:$1|íméébéanọr|íméébéanọr $1}} nke á.",
        "category-article-count-limited": "Nkeá {{PLURAL:$1|ihü dị|ihü $1 dị}} na ébéanọr nkeá.",
        "category-file-count": "{{PLURAL:$2|Ébéonọr nka nwèrè náni usòrò nka.|{{PLURAL:$1|Usòrò nka|Usòrò nke $1}} di na ímé ébéonọr nga, shí háníle di $2.}}",
        "category-file-count-limited": "Nkeá {{PLURAL:$1|usòrò dị|usòrò $1 dị}} na ébéanọr nkeá.",
-       "listingcontinuesabbrev": "mewá.",
+       "listingcontinuesabbrev": "gàzi.",
        "index-category": "Ẹdẹle Ihü",
        "noindex-category": "Ihü ẹdẹlebu",
        "broken-file-category": "Ihü nwere jkọdọ na ga fail gbajírí",
        "about": "Màkà",
        "article": "Ihü ihe dị",
        "newwindow": "(o na mepo na onyonyo ohúrù)",
-       "cancel": "Kàchá",
+       "cancel": "Hapụ̀",
        "moredotdotdot": "Ozókwá...",
        "mypage": "Ihü",
-       "mytalk": "Okwu",
-       "anontalk": "Owu màkà IP nká",
-       "navigation": "Otú Uzọr",
+       "mytalk": "Nkàta",
+       "anontalk": "Nkàta",
+       "navigation": "Nturuụzọ̀",
        "and": "&#32;ná",
        "faq": "FAQ",
        "actions": "Mmèmé",
-       "namespaces": "Ámááhà",
+       "namespaces": "Ahàm̀bara",
        "variants": "Nke ichè ichè",
        "errorpagetitle": "Nsogbú",
        "returnto": "Ganata na $1.",
        "tagline": "Oshị {{SITENAME}}",
        "help": "Inyeáká",
-       "search": "Chọwa",
-       "searchbutton": "Chọwa",
+       "search": "Tùwe",
+       "searchbutton": "Tùwe",
        "go": "Gá",
        "searcharticle": "Gá",
        "history": "Ịta ihüá",
        "history_short": "Ịta",
        "updatedmarker": "ihe gáráníru ké mgbe m byàrà nga mbu",
-       "printableversion": "Nkè I nweríkí dotié",
+       "printableversion": "Ùdì ǹke mbifụ̀",
        "permalink": "Jikodo ekechịrị",
        "print": "Dotié",
        "view": "Lèzí",
+       "view-foreign": "Zi nà $1",
        "edit": "Mèzi",
        "create": "Ké",
        "delete": "Kàcha",
        "protect_change": "gbanwe",
        "unprotect": "Nchẹdo mgbanwe",
        "newpage": "Ihü ohúrù",
-       "talkpagelinktext": "Okwu",
+       "talkpagelinktext": "nkàta",
        "specialpage": "Ihü mkpà",
-       "personaltools": "Ngwa nkem",
-       "talk": "Akíkó",
+       "personaltools": "Ngwa ọrụ ònwe",
+       "talk": "Nkpata okà",
        "views": "Há hụrụ ya olé",
-       "toolbox": "Ngwa Oru",
-       "imagepage": "Zi ihü usòrò",
-       "mediawikipage": "Zi ihü ozi",
-       "templatepage": "Zi ihü nkpurụ ihü",
-       "viewhelppage": "Zi ihü I nye áká",
-       "categorypage": "Zi ébé ihü nọr",
-       "viewtalkpage": "Zi akíkó",
-       "otherlanguages": "Na asụsụ ndị ozó",
-       "redirectedfrom": "(Kufùrù shi $1)",
+       "toolbox": "Ngwa ọrụ",
+       "imagepage": "Zìri ihu àfabà",
+       "mediawikipage": "Zìri ihunde ozi",
+       "templatepage": "Zìri ihunde àtụ̀",
+       "viewhelppage": "Zìri ihu nkwàdo",
+       "categorypage": "Zìri ihu ụdàkọ",
+       "viewtalkpage": "Zìri nkàta",
+       "otherlanguages": "Nà asụ̀sụ̀ ndị ọ̀zọ",
+       "redirectedfrom": "(Dupụ̀rụ̀ sì $1)",
        "redirectpagesub": "Kufù ebe ihü nka na ga",
        "lastmodifiedat": "Há rüchàrà na ihü nka na $1, mgbe $2",
        "viewcount": "Ha banyere ihü nka na {{PLURAL:$1|otu|$1 mgbe ole}}.",
        "protectedpage": "Ihü a cẹdolu a cẹdo",
-       "jumpto": "Wuá ébé:",
-       "jumptonavigation": "otú uzọr",
-       "jumptosearch": "chọwa",
+       "jumpto": "Wụ̀ ga:",
+       "jumptonavigation": "nturuụzọ̀",
+       "jumptosearch": "tùwe",
        "view-pool-error": "Ndó, ihe na enye juchàrà ejucha oge nka.\nMadu kachạrạ ndi choro ihu ihü nka.\nBiko chetukwa oge kà oruo mgbe I choro I banyé ihü nka ozor.\n\n$1",
        "pool-timeout": "Ógè e zuole Í ché ncedọ",
        "pool-queuefull": "Pool kyu zùrù",
        "pool-errorunknown": "Nsogbu nke námaghi",
-       "aboutsite": "Maka {{SITENAME}}",
-       "aboutpage": "Project:Ihe owù",
+       "aboutsite": "Màkà {{SITENAME}}",
+       "aboutpage": "Project:Màkà",
        "copyright": "Ihe di ime nọr okpúrụ $1",
        "copyrightpage": "{{ns:project}}:Iwu maka ijë ihe",
        "currentevents": "Ihe ne me ubuwá",
        "currentevents-url": "Project:I ne me ubüwá",
-       "disclaimers": "Ihe anyí chọrọ ki ma",
+       "disclaimers": "Ńwefụ̀aka",
        "disclaimerpage": "Project:Ihe I kweshiri ma",
        "edithelp": "Inyetuáká I rüwa",
-       "mainpage": "Ihü Mbu",
-       "mainpage-description": "Ihü Mbu",
+       "mainpage": "Ihu m̀bụ",
+       "mainpage-description": "Ihu m̀bụ",
        "policy-url": "Project:Iwu",
        "portal": "Ogbako ọtú",
        "portal-url": "Project:Ogbako Ọtú",
-       "privacy": "Iwu maka ndi ichi ichie",
-       "privacypage": "Project:Iwu maka ndi ichi ichie",
+       "privacy": "Ụmẹzù ǹzuzo",
+       "privacypage": "Project:Ụmẹzù ǹzuzo",
        "badaccess": "Nsogbu ébé ha na nyé ike I bàtá",
        "badaccess-group0": "I nwéghị ọdà Í kpárá ihe Í chọrí mė.",
        "badaccess-groups": "Ihe Í chọrí me bu ihe ndi ọ'bànifé nke ntàkírí nọr na {{PLURAL:$2|the group|ótù ọtú}} ne me náni: $1",
        "viewsourceold": "zi mkpurụ",
        "editlink": "mèzi",
        "viewsourcelink": "zi mkpurụ",
-       "editsectionhint": "Rüwa na élu nkeji: $1",
+       "editsectionhint": "Mèzi òkè: $1",
        "toc": "Ihe dị ime",
        "showtoc": "zi",
        "hidetoc": "zonari",
        "site-atom-feed": "$1 ntabì Atom",
        "page-rss-feed": "''$1'' ntabì RSS",
        "page-atom-feed": "''$1'' ntabì Atom",
-       "red-link-title": "$1 (ihü a di gì)",
+       "red-link-title": "$1 (ihu a dị gị̀)",
        "sort-descending": "Dozi shí àlà",
        "sort-ascending": "Dozi shí élú",
        "nstab-main": "Ihü",
-       "nstab-user": "Ihü ọ'bànifé",
+       "nstab-user": "Ihunde òjìème",
        "nstab-media": "Ihü nkà",
        "nstab-special": "Ihü mkpà",
        "nstab-project": "Ihü orürü",
-       "nstab-image": "Usòrò",
+       "nstab-image": "Àfabà",
        "nstab-mediawiki": "Ozi",
        "nstab-template": "Àtụ",
        "nstab-help": "Ihü ebe ha na nye áká",
-       "nstab-category": "Ébéonọr",
+       "nstab-category": "Ụdàkọ",
+       "mainpage-nstab": "Ihu m̀bụ",
        "nosuchaction": "Mmèmé adighi",
        "nosuchactiontext": "Ihe URL chọrọ Í me àdíghị ézíbóté.\nÍ nwèríkí dèfié URL è dèfie, ma ó nwèríkí bụ nà Í sọrọ jikodo nke àdíghị mma.\nIhe a nwèríkí bu ihe ne zi bug di na ngwa nsónùsòrò nke {{SITENAME}} jì à rü.",
        "nosuchspecialpage": "Ihü mkpà nka a nogị",
        "badtitletext": "Íshí ihü Í chọrọ à díghị ézíbóté, efù, mà ȯ dị jikodo di jikodo nke ojö na nke íshí asụsụ-mmékotárí ma wiki-mmékotárí.\nO nwèríkí nwé édé ótù ma nke ozor nke ékwéghị na íshí ihü.",
        "perfcached": "Ómárí á kachẹrẹ na o nwẹrẹ ki a kugwaghị ya na ogẹ di nso. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.",
        "viewsource": "Zi mkpurụ",
+       "viewsource-title": "Zi mkpụrụ $1",
        "actionthrottled": "Mmèmé a puziélé",
        "protectedpagetext": "Ihüá cedolụ maka orürü ạ gáa bá.",
        "viewsourcetext": "Í nwèríkí lé na Í jé mkpurụ ihüá:",
        "virus-scanfailed": "ojëjë orunotụ dàrà (edemede $1)",
        "virus-unknownscanner": "amaghị obubu onyá:",
        "logouttext": "'''I fwuóla ubwá.'''\n\nI nwèríkí jíwá {{SITENAME}} na nke ẹnwéghi áhà, mànà Í nwèríkí <span class='plainlinks'>[$1 bátá òzọr]</span> na áhà Í shị fwüo ma áhà ozọr.\nMàkwá na o dị ihü gi zi kà Í nor kwa ímé, o gi kwüshí mgbe Í sáfùrù cache ihe ishi a gá intanet gi.",
-       "yourname": "Áhà ọ'bànifé:",
+       "yourname": "Ahàǹjìème:",
+       "userlogin-yourname": "Ahàǹjìème",
        "yourpassword": "Okwúngáfè:",
+       "userlogin-yourpassword": "Okwungafè",
        "yourpasswordagain": "Detuari mkpurụ okwu ejị a gafẹ:",
        "yourdomainname": "Obí gi:",
-       "login": "Banyé",
+       "login": "Detùba",
        "nav-login-createaccount": "Banyé / ké buwá",
        "logout": "Fwuör",
        "userlogout": "Fwuör",
        "notloggedin": "I bátà bò",
        "createaccount": "Ké otụ buwa",
        "createaccountmail": "na e-mail",
+       "createacct-benefit-body1": "{{PLURAL:$1|ḿmezi}}",
        "badretype": "Mkpurụ okwu ejị a gafẹ é jëghị.",
        "userexists": "Áhè ọ'bànifé tírí di na áká onye ozor.\nBíkó nwèré áhà nke ozor.",
        "loginerror": "Nsogbu ngbe I choro I bata",
        "accountcreated": "Ndoté è mepólé",
        "createaccount-title": "Okìké bùwá màkà {{SITENAME}}",
        "loginlanguagelabel": "Asụsụ: $1",
+       "pt-login": "Debàta",
+       "pt-createaccount": "Kèta ngụrụòkè",
        "changepassword": "Gbanwe okwu éjị à gáfe",
        "resetpass_header": "Gbanwe okwúngáfè nke bùwá",
        "oldpassword": "Mkpurụ okwu ejị a gafẹ ochië:",
        "retypenew": "Dechákwari mkpurụ okwu ejị a gafẹ nke ohúrù:",
        "resetpass_submit": "Bá okwu éjị gáfè na áhà Í bànyè",
        "changepassword-success": "Mkpurụ okwu ejị a gafẹ a gbanwere nke oma!\nI na á banye...",
+       "botpasswords-label-cancel": "Hapụ̀",
        "resetpass_forbidden": "Okwu éjị à gáfè enwéghịkị gabnwe",
        "resetpass-submit-loggedin": "Gbanwe okwu éjị à gáfe",
-       "resetpass-submit-cancel": "Kàchá",
+       "resetpass-submit-cancel": "Hapụ̀",
        "resetpass-temp-password": "mkpurụ okwu ejị a gafẹ I gi kushi ngwa ngwa:",
        "passwordreset": "Nkuwaria okwúngáfè",
+       "passwordreset-username": "Ahàǹjìème:",
        "passwordreset-emailelement": "Áhà Ọ'banife: \n$1\n\nPasswod nke gi gbanwe: \n$2",
        "changeemail-none": "(efù)",
        "bold_sample": "Mkpúrù èdè íke",
        "subject": "Ihe gbasara/Ishi ahiri",
        "minoredit": "Ihe bu orü ntakírí",
        "watchthis": "Lèwá ihüá",
-       "savearticle": "Domá ihüa",
+       "savearticle": "Dònye ihuâ",
        "preview": "Lètú",
-       "showpreview": "Létu ntakìrí",
-       "showdiff": "Zi ihe gbanwere",
+       "showpreview": "Zìwe nkirimaàtụ̀",
+       "showdiff": "Zi mgbanwè",
        "anoneditwarning": "'''Kpàchákwá anya:''' Ị bághị bo.\nIP gi gí détụ na ákíkó mbu ihü a.",
        "missingcommenttext": "Biko tinyé ótù okwu na àlà nga.",
        "summary-preview": "Hutukwá mmẹkotá:",
        "history-title": "Ákíkó mbu màkà orü nọr na élú \"$1\"",
        "lineno": "Ahiri $1:",
        "compareselectedversions": "Sikwụ orü áká dị",
-       "editundo": "mẹ̀rí àzụ́",
-       "searchresults": "Ndọfùtà nchọwa",
+       "editundo": "mè la àzụ",
+       "searchresults": "Nchọfụ̀ta",
        "searchresults-title": "Ihe futárá nchowá màkà ''$1''",
        "titlematches": "Íshí ihü dàbànyèrè",
        "textmatches": "Mkpụrụ édémédé nwèrè ihü ȯ dị na",
        "shown-title": "Zí $1 {{PLURAL:$1|ihe fútárá|ihe fútárá}} na ótù ihü",
        "viewprevnext": "Lé ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-new": "'''Ké ihü \"[[:$1]]\" na wiki nke á!'''",
-       "searchprofile-articles": "Ihü ihe dị",
-       "searchprofile-images": "Nkaníle",
-       "searchprofile-everything": "Iheníle",
-       "searchprofile-advanced": "Nke kárí",
-       "searchprofile-articles-tooltip": "Chọwa na $1",
-       "searchprofile-images-tooltip": "Chọwa màkà usòrò",
-       "searchprofile-everything-tooltip": "Tùwé ihe nile (na okwu ihü)",
-       "searchprofile-advanced-tooltip": "Chọwa na ímé áhàámá nke gí Í kèrè",
+       "searchprofile-articles": "Ihu ịhe dị̀",
+       "searchprofile-images": "Oke-ǹkà",
+       "searchprofile-everything": "Ịhe níle",
+       "searchprofile-advanced": "Ọ̀kàkaà",
+       "searchprofile-articles-tooltip": "Tùwe na $1",
+       "searchprofile-images-tooltip": "Chọ̀wa àfabà",
+       "searchprofile-everything-tooltip": "Tùwe ịhe níle (mà nà ihu nkàta)",
+       "searchprofile-advanced-tooltip": "Tùwe nà ime ahàm̀bara ntụziri",
        "search-result-size": "$1 ({{PLURAL:$2|mkpurụ edemede 1|$2 mkpurụ edemede}})",
        "search-redirect": "(kúfù $1)",
        "search-section": "(nkeji $1)",
+       "search-category": "(ụdàkọ $1)",
        "search-suggest": "Ị̀ kwèshirí dé: $1",
        "search-interwiki-caption": "Orürü nwanne nwanyị",
        "search-interwiki-default": "$1 nke ziri:",
        "powersearch-togglelabel": "Lechányá:",
        "powersearch-toggleall": "Haníle",
        "powersearch-togglenone": "Efù",
-       "preferences": "Otu ha dosẹrẹ ihe",
-       "mypreferences": "Otú m shị na dose ihem",
+       "preferences": "Ndoziri",
+       "mypreferences": "Ndoziri",
        "prefs-skin": "Akpụkpọ",
        "skin-preview": "Lètú",
        "datedefault": "Otú é shị na dose ihe efù",
-       "prefs-user-pages": "Ihü ọ'bànifé",
+       "prefs-user-pages": "Ihunde òjìème",
        "prefs-personal": "Nkówá ọ'bànifé",
        "prefs-rc": "Mgbánwè ógè nso",
-       "prefs-watchlist": "Ndétụnlé",
+       "prefs-watchlist": "Ùlìle",
        "prefs-watchlist-days-max": "Maximum $1 {{PLURAL:$1|day|days}}",
        "prefs-misc": "Mcheta-ma-mchetaghim",
        "prefs-resetpass": "Gbanwe okwu éjị à gáfe",
        "prefs-email": "Màkà e-mail",
        "prefs-rendering": "Ọdịdị",
-       "saveprefs": "Domá",
+       "saveprefs": "Dònye",
        "prefs-editing": "Írüwa",
-       "searchresultshead": "Chọwa",
+       "searchresultshead": "Tùwe",
        "stub-threshold-disabled": "Ápụgị òkò",
        "timezonelegend": "Nkeji ogẹ:",
        "localtime": "Ogẹ ebeanọr:",
        "timezoneregion-europe": "Alá Bèke",
        "timezoneregion-indian": "Abwädi Ukwu India",
        "timezoneregion-pacific": "Òrìmìlì Pasifik",
-       "prefs-searchoptions": "Màkà nchöwa",
-       "prefs-namespaces": "Áhàámá",
+       "prefs-searchoptions": "Tùwe",
+       "prefs-namespaces": "Ahàm̀bara",
        "default": "nke éjị bịdó",
-       "prefs-files": "Usòrò",
+       "prefs-files": "Àfabà",
        "prefs-custom-css": "CSS nà áká mádu",
        "prefs-custom-js": "JavaScript na áká mádu",
        "youremail": "E-mail:",
-       "username": "Áhà ọ'bànifé:",
+       "username": "{{GENDER:$1|Ahàǹjìème}}:",
        "prefs-memberingroups": "Onyé otu nke {{PLURAL:$1|ọtú|ọtú}}:",
        "yourrealname": "Ézíbóté áhà:",
        "yourlanguage": "Ásụ̀sụ̀:",
        "yournick": "Ndè áhà gi òhúrù:",
        "yourgender": "Nwayi/okpoho ma o nwoke:",
-       "gender-unknown": "Ámákwàghị",
+       "gender-unknown": "M̀gbè ha tụ̀rụ̀ gị aka, ndiriusòrò g'ị jiri okwu ụ̀dịnàètitì a kàtara èkèrè gị m̀gbè o nwèrè ike ị",
        "gender-male": "Òkò",
        "gender-female": "Ányị̀",
        "email": "ozi e-mail",
        "userrights-groupsmember": "Onye ọtú nke:",
        "userrights-reason": "Mgbághapụtà:",
        "group": "Ọtú:",
-       "group-user": "Ọ'bànifé",
+       "group-user": "Òjìème",
        "group-bot": "Bot",
        "group-sysop": "Ndi íshí",
        "group-bureaucrat": "Ọdọzị'obodo",
        "group-suppress": "Aghọ",
        "group-all": "(háníle)",
-       "group-user-member": "ọ'bànifé",
+       "group-user-member": "{{GENDER:$1|òjìème}}",
        "group-autoconfirmed-member": "ọ'bànifé kwé'nà'áká",
        "group-bot-member": "bot",
        "group-sysop-member": "onye íshí",
        "group-bureaucrat-member": "ọdọzị'obodo",
        "group-suppress-member": "aghọ",
-       "grouppage-user": "{{ns:project}}:Ọ'bànifé",
+       "grouppage-user": "{{ns:project}}:Òjìème",
        "grouppage-bot": "{{ns:project}}:Bot",
        "grouppage-sysop": "{{ns:project}}:Ndi Íshí wiki",
        "right-read": "Gụwá ihü",
        "right-edit": "Rüwa ihü",
        "right-createpage": "Ké ihü (nke nadíghị na ihü okwu)",
-       "right-move": "Páfù ihü",
-       "right-movefile": "Páfù usòrò",
+       "right-move": "Papụ̀ ihuâ",
+       "right-movefile": "Papụ̀ àfabà",
        "right-upload": "Tịnyé ihe na nsónùsòrò",
        "right-delete": "Kàchafu ihü",
        "right-bigdelete": "Kàcha ihü nwéré ákíkó mbu dí ógólógó",
        "newuserlogpage": "Ndétu nchétá ihe ọ'bànifé kèrè",
        "rightslog": "Ndetu échìchè íwú ọ'bànifé",
        "action-read": "guwa ihüá",
-       "action-edit": "rüo élu ihüá",
+       "action-edit": "mèzi ihu â",
        "action-createpage": "ké ihü",
-       "action-move": "puzié ihü nke",
+       "action-move": "papụ̀ ihuâ",
        "action-movefile": "puzié usòrò",
        "action-upload": "tinyé njikota èdèa",
        "action-reupload": "tinyé ihe ozor élu njikota èdèa",
        "action-delete": "kàcha ihü nka",
        "nchanges": "$1 {{PLURAL:$1|gbanwere|gbanwere}}",
+       "enhancedrc-history": "ị̀ta",
        "recentchanges": "Mgbánwè ógè nso",
        "recentchanges-legend": "Nràlụ màkà Ihe gbanwere ubwá",
        "recentchanges-feed-description": "Chóputà ihe ógẹ ǹsò na wiki ímé órírí nke á.",
        "recentchanges-label-minor": "Ihe bu orü ntakírí",
        "recentchanges-legend-newpage": "$1 - ihü ohúrù",
+       "rcfilters-savedqueries-cancel-label": "Hapụ̀",
        "rclistfrom": "Zìrí ihe gbanwere ọhúrù shí $3 $2",
        "rcshowhideminor": "orü ntákírí $1",
+       "rcshowhideminor-show": "Zi",
+       "rcshowhideminor-hide": "Zònarị",
        "rcshowhidebots": "bot $1",
+       "rcshowhidebots-show": "Zi",
+       "rcshowhidebots-hide": "Zònarị",
        "rcshowhideliu": "Ndi né ké dị $1 di íme",
+       "rcshowhideliu-show": "Zi",
+       "rcshowhideliu-hide": "Zònarị",
        "rcshowhideanons": "$1 ndi ọ'bànifé nke amághị",
+       "rcshowhideanons-show": "Zi",
+       "rcshowhideanons-hide": "Zònarị",
        "rcshowhidepatr": "$1 orü hä lèrè",
        "rcshowhidemine": "$1 ihe m rürü",
+       "rcshowhidemine-show": "Zi",
+       "rcshowhidemine-hide": "Zònarị",
        "rclinks": "Zí nke mbu $1 gbawere na ubochi gárá nke $2",
-       "diff": "Íchè",
+       "diff": "ichè",
        "hist": "akíkómbu",
        "hide": "Zonari",
        "show": "Zi",
        "newpageletter": "N",
        "boteditletter": "b",
        "number_of_watching_users_pageview": "[$1 {{PLURAL:$1|ọ'bànifé|ọ'bànifé}} ne lé anya]",
+       "rc-change-size-new": "{{PLURAL:$1|byte}} $1 ǹkè mgbanwè fọ̀rọ̀",
        "newsectionsummary": "/* $1 */ nkeji ohúrù",
        "rc-enhanced-expand": "Zi ihe di ime (Í gí nwere JavaScript)",
        "rc-enhanced-hide": "Zonari ihe di ime",
        "sourceurl": "URL mkpọlógwù:",
        "upload-description": "Nkówá usòrò",
        "watchthisupload": "Lèwá ákwúkwó orunotunị",
+       "upload-dialog-button-cancel": "Hapụ̀",
+       "upload-dialog-button-save": "Dònye",
+       "upload-form-label-infoform-categories": "Ụdàkọ",
        "http-read-error": "Nsogbu Í gü HTTP.",
        "upload-curl-error6": "Ènwéghịkị ruó URL",
        "license": "Nkwényé:",
        "license-header": "Nkwényé",
        "nolicense": "Ọ dígì nke áká di.",
-       "imgfile": "usòrò",
+       "imgfile": "àfabà",
        "listfiles": "Ndétu usòrò",
        "listfiles_thumb": "Nvọáká",
        "listfiles_date": "Ǹgụ́ụ̀bọ̀chị̀",
        "listfiles_name": "Áhà",
-       "listfiles_user": "Ọ'bànifé",
+       "listfiles_user": "Òjìème",
        "listfiles_size": "Ívụ",
        "listfiles_description": "Nkówá",
        "listfiles_count": "Ùdị",
-       "file-anchor-link": "Usòrò",
+       "file-anchor-link": "Àfabà",
        "filehist": "Ịta nke usòrò",
-       "filehist-help": "Kpàtá na úbochi/ógè Í zí usòrò ọtụ ȯ dị mgbe áhù.",
+       "filehist-help": "Bìri èhì/ogè k'ị hụ òtù ụ̀fa dị̀ m̀gbè ahụ̀.",
        "filehist-deleteall": "kàcha hanílé",
        "filehist-deleteone": "kàcha",
        "filehist-revert": "gbanwe lá àzú",
-       "filehist-current": "nka",
-       "filehist-datetime": "Afọ/Ogẹ",
-       "filehist-thumb": "Nvọáká",
+       "filehist-current": "dị ùgbu â",
+       "filehist-datetime": "Èhì/Ogè",
+       "filehist-thumb": "Mbọ-aka",
        "filehist-thumbtext": "NvóÁká màkà otù ȯ dị nà $1",
        "filehist-nothumb": "Nvọáká adịghị",
-       "filehist-user": "Ọ'bànifé",
+       "filehist-user": "Òjìème",
        "filehist-dimensions": "Ógólógó na asaá",
        "filehist-filesize": "Ívù usòrò",
-       "filehist-comment": "Okwu-nokwu",
+       "filehist-comment": "Nkwute",
        "imagelinks": "Mgbanwe usòrò",
        "linkstoimage": "{{PLURAL:$1|Ihü nká|Ihü nke $1}} na jikodo gá usòrò nká:",
        "nolinkstoimage": "Àdíghị ihü na jikodo usòrò nke.",
        "unusedtemplates": "Àtụ hè jí gị",
        "unusedtemplateswlh": "jikodo ndi ozor",
        "randompage": "Edemede nkeówúlạ",
+       "randomincategory-category": "Ụdàkọ",
        "randomincategory-submit": "Gá",
        "statistics": "Olìlé ọtụ ihe dị",
        "statistics-header-pages": "Ọmúmú-nà-ńlé ihü",
        "withoutinterwiki-legend": "Na íshí mkpụrụ okwu",
        "withoutinterwiki-submit": "Zi",
        "nbytes": "$1 {{PLURAL:$1|byte|byte di}}",
-       "ncategories": "{{PLURAL:$1|ébéonọr|ébéonọr}} $1",
+       "ncategories": "{{PLURAL:$1|ụdàkọ}} $1",
        "nlinks": "{{PLURAL:$1|jikodo|jikodo}} $1",
        "nmembers": "{{PLURAL:$1|ọ'bànifé|Ndi n'bànifé}} $1",
        "nrevisions": "{{PLURAL:$1|orübà|orübà}} $1",
        "usereditcount": "$1 {{PLURAL:$1|rüwá|orürü}}",
        "usercreated": "Kéré na $1 mgbe $2",
        "newpages": "Ihü ohúrù",
-       "newpages-username": "Áhà ọ'bànifé:",
+       "newpages-username": "Ahàǹjìème:",
        "ancientpages": "Ihü díkárí íchié",
-       "move": "Páfụ",
-       "movethispage": "Páfù ihüá",
+       "move": "Papụ̀",
+       "movethispage": "Papụ̀ ihuâ",
        "notargettitle": "Ntido adighị",
        "pager-newer-n": "{{PLURAL:$1|1 nke ohúrù|$1 nke ohúrù}}",
        "pager-older-n": "{{PLURAL:$1|1 nke ichié |$1 nke ichié}}",
        "apisandbox-results": "Nfụ́fụ̀tà",
        "booksources": "Ébé ákwúkwó shị",
        "booksources-search-legend": "Tuó íshí akwúkwó shì",
+       "booksources-search": "Tùwe",
        "specialloguserlabel": "Ọ'bànifé:",
        "speciallogtitlelabel": "Ishi:",
        "log": "Ndetu-nchétá",
        "prevpage": "Ihü nke búzọr ($1)",
        "allpagesfrom": "Zi ihü bídóró na:",
        "allpagesto": "Zi na ihu ihü ná kwúshí nà:",
-       "allarticles": "Ihü níle",
+       "allarticles": "Ihu níle",
        "allinnamespace": "Ihü níle (ámááhạ $1)",
        "allpagessubmit": "Gá",
-       "categories": "Ébéanọr",
+       "categories": "Ụdàkọ",
        "sp-deletedcontributions-contribs": "ihe rürü di mkpa",
        "linksearch": "Òtú jikodo di èzí",
-       "linksearch-ns": "Áhàámá:",
-       "linksearch-ok": "Chọwa",
+       "linksearch-ns": "Ahàm̀bara:",
+       "linksearch-ok": "Tùwe",
        "linksearch-line": "$1 jikọdọ shí $2",
        "listusers-submit": "Zi",
-       "listusers-noresult": "Ọ hügị ọ'bànifé.",
+       "listusers-noresult": "À chọfụ̀tàhụ̀ghị̀ òjìème.",
        "listusers-blocked": "(kwàchịrị)",
-       "activeusers-noresult": "Ọ hügị ọ'bànifé.",
+       "activeusers-noresult": "À chọfụ̀tàhụ̀ghị̀ òjìème.",
        "listgrouprights-group": "Ọtú",
        "listgrouprights-rights": "Nkwènyé",
        "listgrouprights-members": "(ndetu ndi nọr nga)",
        "listgrouprights-addgroup": "Gbàkọ {{PLURAL:$2|ọtú|ọtú}}: $1",
        "listgrouprights-addgroup-all": "Tìnyé ọtú nílé",
+       "listgrouprights-namespaceprotection-namespace": "Ahàm̀bara",
        "emailuser": "Zi onye á ózí-nsónùsòrò",
        "defemailsubject": "e-mail {{SITENAME}}",
+       "emailusername": "Ahàǹjìème:",
+       "emailusernamesubmit": "Zibànye",
        "emailfrom": "Onye banyere ya:",
        "emailto": "Onye o gi ru:",
        "emailsubject": "Gbàsịrị:",
        "emailmessage": "Ozi:",
        "emailsend": "Zí ozi",
        "emailsent": "E-mail zìrì",
-       "watchlist": "Ndetu ihem ne lé",
-       "mywatchlist": "Ndetu ihem ne lé",
+       "watchlist": "Ùlìle",
+       "mywatchlist": "Ùlìlem",
        "watchlistfor2": "Maka $1 $2",
        "addedwatchtext": "Ihü \"[[:$1]]\" à bányéré [[Special:Watchlist|ndétu ihe Í ne lé]].\nIhe gi gbanwe na ógè gi bya nà ihüá na ihü okwu ya gi di ndétu ngáhù, na ihü gi da na mkpụrụ édé '''sírí íke''' ímé [[Special:RecentChanges|ndétu gbanwere méré na ogè nso]] ka ȯ dí òfelè Í hü ya.",
        "removedwatchtext": "Ihü \"[[:$1]]\" à fwüó lá [[Special:Watchlist|ihe ndétu ihe Í ne lé]].",
        "watchlist-options": "Nrọta ndetu nlènlé",
        "watching": "O na hü...",
        "unwatching": "O mele ka o na á hü kwagi...",
-       "enotif_impersonal_salutation": "ọ'bànifé {{SITENAME}}",
+       "enotif_impersonal_salutation": "Òjìème {{SITENAME}}",
        "enotif_anon_editor": "ọ'bànifé ézíghị ihu $1",
        "created": "kèrè",
        "changed": "gbanwere",
        "maximum-size": "Ívù nke ukwu:",
        "pagesize": "(byte)",
        "restriction-edit": "Mèzi",
-       "restriction-move": "Páfụ",
+       "restriction-move": "Papụ̀",
        "restriction-create": "Ké",
        "restriction-upload": "Tinyénélú",
        "restriction-level-all": "ọtú nke ȯbulà",
        "undeletelink": "lé/dosimá",
        "undeleteviewlink": "lé",
        "undeletecomment": "Mgbághapụtà:",
-       "undelete-search-submit": "Chọwa",
+       "undelete-search-submit": "Tùwe",
        "undelete-show-file-submit": "Eeh",
-       "namespace": "Áhàámá:",
-       "invert": "kwùtúárí ihe áká nọr",
+       "namespace": "Ahàm̀bara:",
+       "invert": "Tụgha ǹke ǹhọ̀rọ",
        "blanknamespace": "(Ḿkpà)",
        "contributions": "Ihe ọ'bànifé rürü",
        "contributions-title": "Orü ọ'bànifé nà $1",
        "mycontris": "Ihem mẹtụrụ na orürü",
        "contribsub2": "Maka $1 ($2)",
-       "uctop": "(ishi)",
+       "uctop": "(dị ùgbu â)",
        "month": "Shi önwa (na nke ndi mbu):",
        "year": "Shi afọr (na ndi nke mbu):",
        "sp-contributions-newbies": "Zí orü áká ọ'bànifé ohúru náni",
        "sp-contributions-deleted": "orü ọ'bànifé gbakashịrị",
        "sp-contributions-uploads": "tinyere na élu.",
        "sp-contributions-logs": "ndetu-nchétá",
-       "sp-contributions-talk": "okwu",
+       "sp-contributions-talk": "nkàta",
        "sp-contributions-search": "Tuó ihe há rürü",
        "sp-contributions-username": "IP mà ọ bu áhà ọ'bànifé:",
-       "sp-contributions-submit": "Chọwa",
+       "sp-contributions-submit": "Tùwe",
        "whatlinkshere": "Ihe na bia nga",
        "whatlinkshere-title": "Ihü ná gá \"$1\" shí jikodo",
        "whatlinkshere-page": "Ihü:",
        "isimage": "jikodo nnunuuche",
        "whatlinkshere-prev": "{{PLURAL:$1|nke dí nà àzú|$1 nke dí nà àzú}}",
        "whatlinkshere-next": "{{PLURAL:$1|nke sọrọ|$1 nke sọrọ}}",
-       "whatlinkshere-links": " jikodo",
+       "whatlinkshere-links": "← òjikọ",
        "whatlinkshere-hideredirs": "$1 nke kufùrù",
        "whatlinkshere-hidetrans": "$1 ọ jè ákwúkwó usòrò",
        "whatlinkshere-hidelinks": "Jikodo $1",
        "ipb-unblock": "Ákwàchịrị áhà ọ'bànifé ma IP",
        "unblockip": "Ákwàchịrị ọ'bànifé",
        "unblocked": "há kwàchịrị [[User:$1|$1]]",
+       "autoblocklist-submit": "Tùwe",
        "ipblocklist": "Ọ'bànifé kwáchírí",
        "blocklist-target": "Ẹ́té",
        "blocklist-expiry": "Gbá ọ́kà",
-       "ipblocklist-submit": "Chọwa",
+       "ipblocklist-submit": "Tùwe",
        "infiniteblock": "ébìébì ùdìdì",
        "anononlyblock": "anon. náni",
        "emailblock": "ha kwàchịrị e-mail",
        "blocklink": "mèché",
        "unblocklink": "a kwadokwàlà",
        "change-blocklink": "gbanwe ngwùgwù",
-       "contribslink": "orürü",
+       "contribslink": "ọrụrụ",
        "blocklogpage": "Ndetù échìchè nke mbàchì",
        "blocklogentry": "kwụchi [[$1]] jí ógè ne $2 $3",
        "unblocklogentry": "àkwáchị gị $1",
        "block-log-flags-noemail": "ha kwàchịrị e-mail",
        "lockdb": "Gbàchí uche nsónùsòrò",
        "unlockbtn": "Ágbàchịkwàlà uche nsónùsòrò",
-       "move-page": "Páfụ $1",
+       "move-page": "Papụ̀ $1",
        "move-page-legend": "Páfù ihü",
        "movepagetext": "Í jí ákwúkwó òchá nke di na àlà gi gȯwá ótù ihü áhà ozor, na ȯ gi kwáfu ákíkó mbu ya na áhà nke ohürù.\nÍshí ihü nke ichié gi bu zi nkúfù gi gá áhà ohürù nke áhụ.\nÍ nwèríkí kúwárí nkúfù nke na ga áhà nke mbu na áká ya.\nȮ bu na Í chȯgị, kpàchákwá ánya Í lé má màkà nkúfù na gbá [[Special:DoubleRedirects|abụọ]] ma [[Special:BrokenRedirects|nkúfù gbájírí]].\nȮ dị na áká gi Í kpáchá ánya na jikodo na gá ébé o kwèshírí ga.\n\nChètákwá na ihü nke áhù '''ágághị''' a pú ȯ bu na ȯ dị ihü nwéré áhà áhù, bèelụ mà ȯ díghị ihe nọr ya na ímé ma ȯ bu nkúfù na onwé ya nke enweghị orürü nke mbu.\nIhe á fùtàrà na Í nwèríkí gọwáríá áhà ihü na nke mbu ya ȯ bu na Í gọrọ ọghọm, na Ì nwéghíkí dé na élú ihü di kwa.\n\n'''Kpàchákwá Ntị!'''\nIhe á nwèríkí bu ngbanwe ukwu mákà ihü ne wu;\nBiko kpàchá kwa ánya Í mà na ihe í ne mé na ógè gbárá mbu mgbè Í gí mé ya.",
        "movepagetalktext": "Ihü owu ya gi fwüor na áká ya na ihü nke gárá '''bèelụ mà:'''\n*Ihü okwu nke énwéghị ihe nọr na ímé ya na ȯ dị nà áhà ohürù nke áhù, mà\n*Í piáfù igbé nọr nà àlà ngá.\n\nNa nke, Í gi páfù na Í mékȯtá ihü nà onwé gi ọ bu nà Í chọrọ.",
        "newtitle": "Gá íshí édémédé nke:",
        "move-watch": "Lèmá ihü ó shị na ihü ȯ na gá",
-       "movepagebtn": "Páfù ihü",
+       "movepagebtn": "Papụ̀ ihuâ",
        "pagemovedsub": "Mpúzié ẹ meelá",
        "movepage-moved": "'''\"$1\" páfùrù Í gá \"$2\"'''",
        "articleexists": " Ihü ótù nwèkwàrà áhà nke áhù, mà áhà Í chọrọ à búghị ézíboté.\nBiko wèré áhà ozor.",
        "move-talk-subpages": "Páfù ihü-n-ímé nke ihü okwu (nè rú $1)",
        "movepage-page-moved": "Ihü $1 a páfùrù gá $2.",
        "movepage-page-unmoved": "Ihü $1 énweghịkị páfù gá $2.",
-       "movelogpage": "Páfù ntínyé",
+       "movelogpage": "Papụ̀ ndenye",
        "movereason": "Mgbághapụtà:",
        "revertmove": "gbanwe lá àzú",
        "delete_and_move_text": "== I kachafu gi me ==\nEbe ihü gé rú \"[[:$1]]\" di kwa.\nI chorí kàchafu ya ka uzor mepo maka mpuzie ne me?",
        "importlogpage": "Hubàtà ndetu",
        "tooltip-pt-userpage": "Ihü ọ'bànifé gi",
        "tooltip-pt-mytalk": "Ihü akíkó gi",
-       "tooltip-pt-preferences": "Otú Í shị na dose ihe gi",
+       "tooltip-pt-preferences": "Ndoziri {{GENDER:|gị}}",
        "tooltip-pt-watchlist": "Ndetu ihü Í ne lé màkà ihe gị gbanwe",
        "tooltip-pt-mycontris": "Ndetù ihe Í rürü",
        "tooltip-pt-login": "Anyi si ka Í gbanyé; chetákwá na nsogbu adighi I gbanye ma Í chógị gbànyé",
        "tooltip-pt-logout": "Fwuör",
        "tooltip-ca-talk": "Akíkó maka ihe di na ihü nka",
-       "tooltip-ca-edit": "Í nwẹríkí rü na ihü nka. Biko jí mkpátá nlélé mgbe Í na domá ihüá",
+       "tooltip-ca-edit": "Mèzi ihuâ",
        "tooltip-ca-addsection": "Binyíte nkeji ohúrù",
-       "tooltip-ca-viewsource": "Ihü nke cẹdolu.\nÍ nwèríkí lé mkpụrụ ya",
+       "tooltip-ca-viewsource": "Ihu â dị̀ ǹchèdò.\nỊ nwẹ̀rẹ̀ ike ị hụ mkpụrụ ya",
        "tooltip-ca-history": "Orü ichié na ihüá",
        "tooltip-ca-protect": "Cẹdolu ihüá",
        "tooltip-ca-unprotect": "Gbánwe ncẹdo ihüá",
        "tooltip-ca-delete": "Bakashia ihüá",
-       "tooltip-ca-move": "Puzie ihüá",
+       "tooltip-ca-move": "Papụ̀ ihuâ",
        "tooltip-ca-watch": "Tìnyé ihü á na ndétu ihe Í ne lé",
        "tooltip-ca-unwatch": "Kwáfụ ihüá shí ndetu ihe m ne lé",
-       "tooltip-search": "Chọwa {{SITENAME}}",
+       "tooltip-search": "Tu nà {{SITENAME}}",
        "tooltip-search-go": "Gá na ihü nwere kwa áhà nke ma o di",
        "tooltip-search-fulltext": "Chọwa na ihü maka mpkurụ okwu á",
-       "tooltip-p-logo": "Ga na ihü mbu",
-       "tooltip-n-mainpage": "Ga na ihü mbu",
+       "tooltip-p-logo": "Ga nà ihu m̀bụ",
+       "tooltip-n-mainpage": "Jèe ihu m̀bụ",
        "tooltip-n-mainpage-description": "Ga na ihü mbu",
        "tooltip-n-portal": "Maka orürü, gi ka Í nweríkí mé, ébé ha na tú ihe",
        "tooltip-n-currentevents": "Tuó ákíkó mbu màkà ihe ne me ubwá",
        "file-info-gif-looped": "etemte",
        "newimages-legend": "Nzàtà",
        "noimages": "Ọ díghì ihe di ngá Í lé.",
-       "ilsubmit": "Chọwa",
+       "ilsubmit": "Tùwe",
        "bydate": "shi afọ",
        "just-now": "ùgbú ùgbúa",
        "bad_image_list": "Ọtụ ȯ dị détùrù ngá:\n\nNání ndétu ihe (áhirí jí * bídó) dị na ihe ȯ chọrọ.\nJikodo bu nke mbu na áhìrì gí jikodo ya nà ȧkwúkwó orünotu di njö.\nJikodo nke gị byá àzú na áhìrì nke òfu á bu nke nwéríkí gáfè, díkà ihü ébé ȧkwúkwó orünotu áhu di ímé áhìrì ya.",
        "exif-imagelength": "Ógólógó",
        "exif-orientation": "Ívú nà àsáa",
        "exif-imagedescription": "Íshí nhuunuche",
+       "exif-software": "Ndiriusòrò ejìème",
        "exif-artist": "Odé ákwụ́kwọ́",
        "exif-exifversion": "Ùdị Exif",
        "exif-colorspace": "Ámá àgwà",
        "exif-citydest": "Ámá ukwu ziri",
        "exif-writer": "Òdìdè",
        "exif-languagecode": "Ásụ̀sụ̀",
-       "exif-iimcategory": "Ébéonọr",
+       "exif-iimcategory": "Ụdàkọ",
        "exif-label": "Ọdụ",
        "exif-orientation-1": "Nkịtị",
        "exif-exposureprogram-1": "Ònyèmáká",
        "exif-urgency-normal": "Nkịtị ($1)",
        "exif-urgency-low": "Nàlà ($1)",
        "exif-urgency-high": "Nélú ($1)",
-       "namespacesall": "nke níle",
+       "namespacesall": "ha níle",
        "monthsall": "nke níle",
        "recreate": "Ké ya ohúrù",
        "confirm_purge_button": "Ngwanu",
        "version-entrypoints-header-entrypoint": "Ébé ọ̀bụ̀bà",
        "version-entrypoints-header-url": "URL",
        "fileduplicatesearch-filename": "Áhà usòrò:",
-       "fileduplicatesearch-submit": "Chọwa",
+       "fileduplicatesearch-submit": "Tùwe",
        "specialpages": "Ihü mkpà",
        "specialpages-group-other": "Ihü mkpà nke ozor",
        "specialpages-group-login": "Banyé / ké buwa",
        "diff-form": "'''àhú'''",
        "dberr-problems": "Ndó! Ámá nka nwere nsogbu ime ime.",
        "htmlform-required": "Ọgụgụ nke gi dị",
-       "htmlform-submit": "Dànyé",
+       "htmlform-submit": "Zibànye",
        "htmlform-reset": "Emekwàlà gbanwere",
        "htmlform-selectorother-other": "Nke ozor",
+       "restore-count-files": "{{PLURAL:$1|1 àfabà |àfabà $1}}",
        "revdelete-content-hid": "ihe zọ̀nàri",
        "rightsnone": "(efù)",
+       "feedback-cancel": "Hapụ̀",
        "feedback-close": "Ọméchá.",
        "feedback-message": "Ozi:",
-       "searchsuggest-search": "Chọwa",
+       "feedback-submit": "Zibànye",
+       "searchsuggest-search": "Tu nà {{SITENAME}}",
        "expand_templates_ok": "Ngwanu",
+       "pagelang-submit": "Zibànye",
        "special-characters-group-latin": "Latin",
-       "special-characters-group-latinextended": "Latin dọrọ",
+       "special-characters-group-latinextended": "Latin dọsàrà",
        "special-characters-group-ipa": "IPA",
        "special-characters-group-symbols": "Nkárí",
        "special-characters-group-greek": "Greek",
index 7e86b3a..80cf726 100644 (file)
                        "Tusholi"
                ]
        },
-       "tog-underline": "ТIатовжама кIала така хьакхар:",
-       "tog-hideminor": "Къайладаккха зӏамига дола хувцамаш керда хувцамашта юкъера",
-       "tog-hidepatrolled": "Къайладаккха ха дера чакхдаьнна дола (патрулированные) хувцамаш керда хувцамашта юкъера",
-       "tog-newpageshidepatrolled": "Ð\9aÑ\8aайлаÑ\8fÑ\8cккÑ\85а Ñ\85а Ð´ÐµÑ\80а Ñ\87акÑ\85Ñ\8aÑ\8fнна Ð¹Ð¾Ð»Ð° (паÑ\82Ñ\80Ñ\83лиÑ\80ованные) оагIонаш керда оагIонашта юкъера",
-       "tog-hidecategorization": "Ð\9aÑ\8aайлаÑ\8fÑ\85а Ð¾Ð°Ð³Ó\80онай ÐºÐ°Ñ\82егоÑ\80еш",
-       "tog-extendwatchlist": "Хьашеръяь йола зем бара список, массадола хувцамаш ше чулоацаш, тIеххьара даь хувцамаш хинна ца Iеш.",
-       "tog-usenewrc": "Ð¥Ñ\83вÑ\86амаÑ\88 Ñ\82оабаÑ\88 ÐµÑ\88 Ð»ÐµÐ»Ð°Ð´Ðµ ÐºÐµÑ\80да Ð½Ð¸Ð¹Ñ\81даÑ\80аÑ\88ка Ð°, Ð·ÐµÐ¼ Ð±Ð°Ñ\80а Ñ\81пиÑ\81ка чу а",
-       "tog-numberheadings": "Ð\90вÑ\82омаÑ\82иÑ\87еÑ\81ки Ð½Ñ\83меÑ\80аÑ\86и Ñ\85Ñ\8cае Ð´Ð¾Ð°ÐºÑ\8aой Ñ\86IеÑ\80ашта",
-       "tog-showtoolbar": "Ð\93IиÑ\80Ñ\81ий Ð¿Ð°Ð½ÐµÐ»Ñ\8c Ñ\85Ñ\8cаÑ\85Ñ\8cокÑ\85а Ñ\85Ñ\83вÑ\86ам Ð±ÐµÑ\87 хана",
+       "tog-underline": "ТIатовжамá кIала така хьакхар:",
+       "tog-hideminor": "Къайладаха зӏамагIа хувцамаш керда хувцамашта юкъера",
+       "tog-hidepatrolled": "Къайладаха техка чакхдаьнна дола (патрулированные, проверенные) хувцамаш керда хувцамашта юкъера",
+       "tog-newpageshidepatrolled": "Ð\9aÑ\8aайлаÑ\8fÑ\85а Ñ\82еÑ\85ка Ñ\87акÑ\85Ñ\8aÑ\8fнна Ð¹Ð¾Ð»Ð° (паÑ\82Ñ\80Ñ\83лиÑ\80ованнÑ\8bе, Ð¿Ñ\80овеÑ\80енные) оагIонаш керда оагIонашта юкъера",
+       "tog-hidecategorization": "Ð\9aÑ\8aайлаÑ\8fÑ\85а Ð¾Ð°Ð³Ó\80оний Ð¾Ð°Ð³Ó\80аÑ\82аш",
+       "tog-extendwatchlist": "Хьашеръе зéма хьаязъяьр шедола хувцамаш юкъелоацаш, тIехьара даь хувцамаш хинна ца Iеш.",
+       "tog-usenewrc": "Ð¥Ñ\83вÑ\86амеÑ\85 Ñ\82оабаÑ\88 Ðµ ÐºÐµÑ\80да Ð½Ð¸Ð¹Ñ\81даÑ\80аÑ\88ка Ð° Ð·Ã©Ð¼Ð° Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80а чу а",
+       "tog-numberheadings": "Ше-Ñ\88егIа Ð½Ð¾Ð¼ÐµÑ\80аÑ\88 Ñ\83вÑ\82Ñ\82ае ÐºÐµÐ¿Ð°ÐºÐµÑ\80Ñ\82ошта",
+       "tog-showtoolbar": "Ð\9aеÑ\87ала Ð¿Ð°Ð½ÐµÐ»Ñ\8c Ñ\85Ñ\8cаÑ\85Ñ\8cокÑ\85а Ñ\85Ñ\83вÑ\86ам Ð±ÐµÑ\87а хана",
        "tog-editondblclick": "Нисъе оагӀонаш шозза IотӀатоӀаеча (JavaScript)",
        "tog-editsectiononrightclick": "Нийсде дáкъа шозза дахка аьттехьара тоIаер тӀатоӀайича дáкъа цIера тIа (JavaScript)",
        "tog-watchcreations": "Зем беш йола оагIонашта а файлашта а тIатоха аз хьаяь оагIонаши чуяьккха файлаши",
        "tog-watchdeletion": "Зем беш йола оагIонашта а файлашта а тIатоха аз дIаяьккха оагIонаши файлаши",
        "tog-minordefault": "Массаза зIамига долаш санна белгалде хувцамаш.",
        "tog-previewontop": "Хьалххе бӀаргтохар хьагойта хувцама кора хьалхашкахь",
-       "tog-previewonfirst": "Хувцама дехьавоалаш хан хьалххе бӀаргтохар хьагойта",
-       "tog-enotifwatchlistpages": "ЭлекÑ\82Ñ\80онни Ð¿Ð¾Ñ\87Ñ\82е Ð³Iолла Ñ\81ога Ñ\85оам Ð±Ðµ Ð·ÐµÐ¼ Ð±ÐµÑ\88 Ð¹Ð¾Ð»Ð° Ð¾Ð°Ð³IонаÑ\88 Ð° Ñ\84айлаÑ\88 Ñ\85Ñ\83вÑ\86аÑ\80аÑ\85",
-       "tog-enotifusertalkpages": "ЭлекÑ\82Ñ\80онни Ð¿Ð¾Ñ\87Ñ\82е Ð³Iолла Ñ\81ога Ñ\85оам Ð±Ðµ са дувца оттадара оагIув хийцача",
-       "tog-enotifminoredits": "Ð\9eагIонаÑ\88Ñ\82а Ð° Ñ\84айлаÑ\88Ñ\82а Ð´Ð°Ñ\8c хувцамаш геттара зIамига дале а хоам бе сога",
-       "tog-enotifrevealaddr": "ДIахайта хоамбараш чу бIаргадейта са почта адрес",
-       "tog-shownumberswatching": "ШоаÑ\88 Ð·ÐµÐ¼ Ð±Ñ\83 Ð¾Ð°Ð³IонаÑ\88Ñ\82а Ñ\8eкÑ\8aе ÐµÑ\80 Ð¾Ð°Ð³IÑ\83в Ñ\87Ñ\83Ñ\8fÑ\8cккÑ\85а Ð´Ð¾Ð°ÐºÑ\8cоÑ\88Ñ\85оÑ\88а Ñ\82аÑ\8cÑ\80аÑ\85Ñ\8c гойта",
+       "tog-previewonfirst": "Хувцам бе аьнна дехьаваьлча хьалххе бӀаргтохар хьахьокха",
+       "tog-enotifwatchlistpages": "ЭлекÑ\82Ñ\80онни Ð¿Ð¾Ñ\87Ñ\82е Ð³Iолла Ñ\85оам Ð±Ðµ Ñ\81ога Ð½Ð°Ð³Ð°Ñ\85Ñ\8c Ð°Ð· Ð·ÐµÐ¼ Ð±ÐµÑ\88 Ð¹Ð¾Ð»Ð° Ð¾Ð°Ð³IонаÑ\88и Ñ\84айлаÑ\88и Ñ\86Ñ\85Ñ\8cанне Ñ\85ийÑ\86аÑ\87а",
+       "tog-enotifusertalkpages": "ЭлекÑ\82Ñ\80онни Ð¿Ð¾Ñ\87Ñ\82е Ð³Iолла Ñ\85оам Ð±Ðµ Ñ\81ога са дувца оттадара оагIув хийцача",
+       "tog-enotifminoredits": "Ð\9eагIонаÑ\88Ñ\82еи Ñ\84айлаÑ\88Ñ\82еи Ð´Ð°Ñ\8c Ñ\85инна хувцамаш геттара зIамига дале а хоам бе сога",
+       "tog-enotifrevealaddr": "ДӀабӀаргадайта са поштан цӀай нахá дӀатӀаухача цхьа хӀама хайташ долча хоамаш чу",
+       "tog-shownumberswatching": "Ð\95Ñ\80 Ð¾Ð°Ð³IÑ\83в Ñ\88оаÑ\88 Ð·ÐµÐ¼Ð±ÐµÑ\87а Ð¾Ð°Ð³IонаÑ\88Ñ\82а Ñ\8eкÑ\8aеÑ\8fÑ\8cккÑ\85а Ð±Ð¾Ð»Ñ\87а Ð´Ð¾Ð°ÐºÑ\8cоÑ\88Ñ\85ой Ñ\82аÑ\8cÑ\80аÑ\85Ñ\8c Ñ\85Ñ\8cагойта",
        "tog-oldsig": "Хьа карара кулг яздар:",
        "tog-fancysig": "Кулг яздара ший йола вики-разметка (автоматически тIахьожаярг йоацаш)",
-       "tog-uselivepreview": "Ð\9fайда Ñ\8dÑ\86а Ñ\81иÑ\85а Ð´Ð¾Ð»Ð° Ñ\85Ñ\8cалÑ\85Ñ\85е Ð±IаÑ\80гÑ\82оÑ\85аÑ\80",
+       "tog-uselivepreview": "Ð¥Ñ\8cаÑ\85Ñ\8cокÑ\85а Ñ\85Ñ\8cалÑ\85Ñ\85е Ð±Ó\80аÑ\80гÑ\82оÑ\85аÑ\80 Ð¾Ð°Ð³Ó\80Ñ\83в Ñ\8eÑ\85а Ñ\85Ñ\8cа Ð° Ñ\86а ÐµÐ»Ð°Ñ\88",
        "tog-forceeditsummary": "ДIахьалхадаккха, нагахьа санна хувцама йоазонца сурт оттадара моттиг хьалъйизанза яле",
-       "tog-watchlisthideown": "Са Ð·ÐµÐ¼ Ð±Ð°Ñ\80а Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80 Ñ\87Ñ\83Ñ\80а Ñ\85Ñ\83вÑ\86амаÑ\88 къайладаха",
-       "tog-watchlisthidebots": "Ð\97ем Ð±Ð°Ñ\80а Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80 Ñ\87Ñ\83Ñ\80а Ð±Ð¾Ñ\82ий Ñ\85Ñ\83вÑ\86амаÑ\88 къайладаха",
-       "tog-watchlisthideminor": "Са Ð·ÐµÐ¼ Ð±Ð°Ñ\80а Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80 Ñ\87Ñ\83Ñ\80а Ð·Iамига Ñ\85Ñ\83вÑ\86амаÑ\88 къайладаха",
-       "tog-watchlisthideliu": "ШоаÑ\88 Ñ\85Ñ\8cабайзийÑ\82а Ð´Ð¾Ð°ÐºÑ\8aоÑ\88Ñ\85оÑ\88а Ñ\85Ñ\83вÑ\86амаÑ\88 ÐºÑ\8aайладаÑ\85а Ð·ÐµÐ¼ Ð±Ð°Ñ\80а Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80 чура",
+       "tog-watchlisthideown": "Ð\90з Ð´Ð°Ñ\8c Ñ\85Ñ\83вÑ\86амаÑ\88 Ð·Ã©Ð¼Ð° Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80а Ñ\87Ñ\83Ñ\80а къайладаха",
+       "tog-watchlisthidebots": "Ð\91оÑ\82аÑ\88 Ð´Ð°Ñ\8c Ñ\85Ñ\83вÑ\86амаÑ\88 Ð·Ã©Ð¼Ð° Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80а Ñ\87Ñ\83Ñ\80а къайладаха",
+       "tog-watchlisthideminor": "Ð\97Iамига Ñ\85Ñ\83вÑ\86амаÑ\88 Ð·Ã©Ð¼Ð° Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80а Ñ\87Ñ\83Ñ\80а къайладаха",
+       "tog-watchlisthideliu": "РажаÑ\87а Ñ\87Ñ\83баÑ\8cннабоаÑ\86аÑ\87а Ð´Ð¾Ð°ÐºÑ\8aоÑ\88Ñ\85оÑ\88а Ð´Ð°Ñ\8c Ñ\85Ñ\83вÑ\86амаÑ\88 ÐºÑ\8aайладаÑ\85а Ð·ÐµÐ¼Ð° Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80а чура",
        "tog-watchlisthideanons": "ЦIийоацача доакъошхоша хувцамаш къайладаха зем бара хьаязъяьр чура",
        "tog-watchlisthidepatrolled": "Ха даь дола хувцамаш къайладаха зéма хьаязъяьра чу",
        "tog-watchlisthidecategorization": "ОагӀонашта оагIаташ тохар къайладаккха",
@@ -55,8 +55,8 @@
        "tog-useeditwarning": "Хоамбе хьадаь хувцамаш дӀа а ца яздеш аз болх дӀаберзабеча хана",
        "underline-always": "Даиман",
        "underline-never": "ЦIаккха",
-       "underline-default": "Ð\91Ñ\80аÑ\83зеÑ\80а Ð³IиÑ\80Ñ\81аÑ\88 Ñ\82оаÑ\8fÑ\80аÑ\88 Ð»ÐµÐ»Ð°е",
-       "editfont-style": "Хувцама моттиге шрифта тайпа:",
+       "underline-default": "Ð\91Ñ\80аÑ\83зеÑ\80а Ð¾Ñ\82Ñ\82амаÑ\88 Ð»ÐµÐ»Ð°Ð´е",
+       "editfont-style": "Хувцам беча моттигера шрифта тайпа:",
        "editfont-monospace": "Цхьатарра йоаца шрифт",
        "editfont-sansserif": "Белгало йоаца шрифт",
        "editfont-serif": "Белгало йола шрифт",
        "december-date": "Чан-тар $1",
        "period-am": "ДЦ",
        "period-pm": "ДТ",
-       "pagecategories": "{{PLURAL:$1|1=ОагIат|ОагIаташ}}",
-       "category_header": "«$1» яхача оагIата чура оагIонаш",
-       "subcategories": "КIалоагIаташ",
-       "category-media-header": "\"$1\" яхача оагIата чура файлаш",
-       "category-empty": "''Ер оагIат хӀанза яьсса я (цхьаккха оагIонаш е файлаш йоацаш).''",
-       "hidden-categories": "{{PLURAL:$1|1=Къайла оагIат|Къайла оагIаташ}}",
-       "hidden-category-category": "Ð\9aÑ\8aайла ÐºÐ°Ñ\82егоÑ\80еш",
+       "pagecategories": "{{PLURAL:$1|1=ОагӀат|ОагӀаташ}}",
+       "category_header": "«$1» яхача оагӀата чура оагIонаш",
+       "subcategories": "КӀалоагӀаташ",
+       "category-media-header": "\"$1\" яхача оагӀата чура файлаш",
+       "category-empty": "''Ер оагӀат хӀанза яьсса я (цхьаккха оагIонаш е файлаш яц укхаза).''",
+       "hidden-categories": "{{PLURAL:$1|1=Къайла оагӀат|Къайла оагӀаташ}}",
+       "hidden-category-category": "Ð\9aÑ\8aайла Ð¾Ð°Ð³Ó\80аÑ\82аш",
        "category-subcat-count": "{{PLURAL:$2|Укх оагIата чу я алхха ер кIалоагIат.|Укх оагIата чу гуш я $2-нен юкъера $1 {{PLURAL:$1|кIалоагIат}} }}",
        "category-subcat-count-limited": "Укх категори чу {{PLURAL:$1|кIалхара категори|$1 кIалхара категореш}} я.",
        "category-article-count": "{{PLURAL:$2|Укх оагIата чу цаI мара оагIув яц.|Укх оагIата чу я $2 оагӀув, царех оагӀонгахьа {{PLURAL:$1|хьагойт $1 оагӀув}}}}",
-       "category-article-count-limited": "УкÑ\85 ÐºÐ°Ñ\82егоÑ\80и Ñ\87Ñ\83 {{PLURAL:$1|$1 Ð¾Ð°Ð³Ó\80Ñ\83в Ñ\8f|1=Ñ\86аI оагӀув мара яц}}.",
-       "category-file-count": "{{PLURAL:$2|Укх оагIата чу цаI мара файл яц.|Укх оагIата чу долча $2 файлах {{PLURAL:$1|1=хьагойт $1 файл}} }}",
+       "category-article-count-limited": "УкÑ\85 Ð¾Ð°Ð³Ó\80аÑ\82а Ñ\87Ñ\83 {{PLURAL:$1|$1 Ð¾Ð°Ð³Ó\80Ñ\83в Ñ\8f|1=Ñ\86Ñ\85Ñ\8cа оагӀув мара яц}}.",
+       "category-file-count": "{{PLURAL:$2|Укх оагӀата чу цаI мара файл яц.|Укх оагӀата чу йолча $2 файлах {{PLURAL:$1|1=хьагуш я $1 файл}} }}",
        "category-file-count-limited": "Укх категори чу {{PLURAL:$1|$1 файл|$1 файлаш|1=цаI мара файл яц}}.",
        "listingcontinuesabbrev": "(дIахо)",
        "index-category": "Индекс оттаеш оагIонаш",
        "anontalk": "Дувца оттадар",
        "navigation": "Навигаци",
        "and": "&#32;а",
-       "faq": "Ð\9aÐ\9bÐ¥",
+       "faq": "Ð\9aаÑ\81Ñ\82-каÑ\81Ñ\82Ñ\82а Ñ\82елаÑ\88 Ð´Ð¾Ð»Ð° Ñ\85аÑ\82Ñ\82аÑ\80аÑ\88",
        "actions": "Ардамаш",
        "namespaces": "ЦIерий аренаш",
        "variants": "Эршаш",
        "tagline": "Кечал я укхазара: {{grammar:genitive|{{SITENAME}}}}",
        "help": "Новкъoстал",
        "search": "Лахаp",
-       "searchbutton": "Хьалáха",
+       "searchbutton": "Хьалаха",
        "go": "Дехьавала",
        "searcharticle": "Дехьавала",
        "history": "Истори",
        "history_short": "Истори",
-       "updatedmarker": "Со Ñ\82IеÑ\85Ñ\85Ñ\8cаÑ\80а Ñ\83кÑ\85аз Ñ\85иннаÑ\87Ñ\83л Ñ\82IеÑ\85Ñ\8cагIа ÐºÐµÑ\80дадаÑ\8cккÑ\85ад",
+       "updatedmarker": "кеÑ\80дадаÑ\8cккÑ\85ад Ñ\81о Ñ\82IеÑ\85Ñ\85Ñ\8cаÑ\80а Ñ\83кÑ\85аза Ñ\85иннаÑ\87Ñ\83л Ñ\82IеÑ\85Ñ\8cагIа",
        "printableversion": "Зарба тохара эрш",
        "permalink": "Массаза болх бу тӏатовжам",
        "print": "Зарба тоха",
        "categorypage": "Категорен оагIон бIаргтоха",
        "viewtalkpage": "Дувца оттадара бIаргтоха",
        "otherlanguages": "Кхыча меттаех",
-       "redirectedfrom": "($1 дIа-сахьожаяьй укхаз)",
-       "redirectpagesub": "Ð\9eагIÑ\83в-дIа-Ñ\81аÑ\85Ñ\8cожадаÑ\80",
+       "redirectedfrom": "($1 яха оагIув дIа-хьа хьожаяьй укхаза)",
+       "redirectpagesub": "Ð\94Iа-Ñ\85Ñ\8cа Ñ\85Ñ\8cожаваÑ\80а Ð¾Ð°Ð³IÑ\83в",
        "redirectto": "ДIа-хьа хьожавар укхаза:",
        "lastmodifiedat": "Ер оагӀув тӀеххьара хийца хиннай укх ха́на: $1, $2.",
        "viewcount": "Укх оагIонга хьежа хиннаб $1{{PLURAL:$1|-зза}}.",
        "pool-queuefull": "ДIахаттарий гулдер хьалдизад",
        "pool-errorunknown": "Довзаш доаца гӀалат",
        "poolcounter-usage-error": "Пайда эцара гIалат: $1",
-       "aboutsite": "{{grammar:genitive|{{SITENAME}}}} лаьца дар",
+       "aboutsite": "Википедех лаьца",
        "aboutpage": "Project:Сурт оттадар",
        "copyright": "Чулоацамá тIакхоачилга да $1 яхача лицензе бокъоношца, нагахь санна кхыдар белгалдаь деце.",
        "copyrightpage": "{{ns:project}}:Автора бокъонаш",
        "policy-url": "Project:Бокъонаш",
        "portal": "Юкъара ков",
        "portal-url": "Project:Юкъара ков",
-       "privacy": "КъайлагIара хIамай политика",
+       "privacy": "КъайлагIарча хIамай политика",
        "privacypage": "Project:КъайлагIара хIамай политика",
        "badaccess": "ТIакхоачилга гӀалат",
        "badaccess-group0": "Оаш дIадийха хинна ардам кхоачашде йиш яц шун.",
        "versionrequiredtext": "Укх оагIонца болх бергболаш $1 версех йола MediaWiki эша. Хьажа [[Special:Version|програмни Iалашдарах бола хоамага]].",
        "ok": "Мег",
        "retrievedfrom": "Хьаст — «$1»",
-       "youhavenewmessages": "{{PLURAL:$3|Хьога денад}} $1 ($2).",
+       "youhavenewmessages": "{{PLURAL:$3|Хьога}} $1 бéнаб ($2).",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|Хьога кхаьчад}} $1 {{PLURAL:$3|1=$3 доакъашхочунгара|$3 доакъашхоштагара|1=кхыволча доакъашхочунгара}} ($2).",
-       "newmessageslinkplural": "{{PLURAL:$1|керда хоам|999=керда хоамаш}}",
+       "newmessageslinkplural": "{{PLURAL:$1|керда хоам}}",
        "newmessagesdifflinkplural": "{{PLURAL:$1|тӀехьара хувцам|999=тӀехьара хувцамаш}}",
        "youhavenewmessagesmulti": "Хьога кхаьчад керда хоамаш $1 чу",
        "editsection": "нийсде",
        "nstab-mediawiki": "Хоамбар",
        "nstab-template": "Ло",
        "nstab-help": "Новкъостал",
-       "nstab-category": "ОагIат",
+       "nstab-category": "ОагӀат",
        "mainpage-nstab": "Керттера",
        "nosuchaction": "Цу тайпара ардам дац",
        "nosuchspecialpage": "Изза мо гIулакха оагӀув яц",
        "databaseerror-function": "Функци: $1",
        "databaseerror-error": "ГIалат: $1",
        "missing-article": "Дарий гуллам чу дIайийха текст яц укх оагIон «$1» чура $2 корадар дезаш хинна.\n\nИз мо гIалат нийсалуш хул тишъенна тIахьожаярга гIолла дIадаьккха оагӀон хувцама истори тӀа дехьавала гӀертача.\n\nНагахьа санна из иштта децe, шоана программни Ӏалашдар чу гIалат кораяь хила мега.\nДехар да, цу гIулакха хоам бе цхьа [[Special:ListUsers/sysop|мазаурхалдерчунга]], укхазар URL белгаляьккха.",
-       "missingarticle-rev": "(верси № $1)",
+       "missingarticle-rev": "(эрш № $1)",
        "missingarticle-diff": "(башхало: $1, $2)",
        "internalerror": "Чура гӀалат",
        "internalerror_info": "Чура гӀалат: $1",
        "welcomeuser": "Маьрша воагIалва, доакъашхо $1!",
        "yourname": "Дагара йоазон цIи:",
        "userlogin-yourname": "Доакъашхочун цӀи",
-       "userlogin-yourname-ph": "Iочуязъе хьай учёта яздара (доакъашхочун) цӀи",
+       "userlogin-yourname-ph": "Iочуязъе хьай дагара йоазон цӀи",
        "createacct-another-username-ph": "Iочуязъе доакъашхочун цӀи",
-       "yourpassword": "Ð\9aÑ\8aайладIоагIа:",
+       "yourpassword": "Ð\9fаÑ\80олÑ\8c:",
        "userlogin-yourpassword": "Пароль",
        "userlogin-yourpassword-ph": "Ӏочуязъе хьай пароль",
        "createacct-yourpassword-ph": "Ӏочуязъе пароль",
-       "yourpasswordagain": "ЮÑ\85аÑ\8fзде ÐºÑ\8aайладIоагIа:",
+       "yourpasswordagain": "Ð\9aÑ\85Ñ\8b Ñ\86Ñ\85Ñ\8cаÑ\8cкÑ\85аза Ð¿Ð°Ñ\80олÑ\8c Ñ\8fзÑ\8aÑ\8fÑ\80:",
        "createacct-yourpasswordagain": "Бакъйе пароль",
        "createacct-yourpasswordagain-ph": "Кхы цхьаькхаза Ӏочуязъе пароль",
        "userlogin-remembermypassword": "Ражача чувиса",
        "userlogout": "Аравала/яла",
        "notloggedin": "Ражача чудаьннадац шо",
        "userlogin-noaccount": "Дагара йоазув деце хьога?",
-       "userlogin-joinproject": "ДIахоттале {{SITENAME}} яхача проекта",
+       "userlogin-joinproject": "ДIахоттале Википедех",
        "createaccount": "Дагара йоазув хьакхолла",
        "userlogin-resetpassword-link": "Тӏеракхосса езий хьа пароль?",
        "userlogin-helplink2": "Ражеча чувалара новкъостал",
        "headline_tip": "2-гӀа лагӀара дáкъа цIи",
        "nowiki_sample": "Укхаза хувца езаш йоаца текст хьачуоттае",
        "nowiki_tip": "Теркал ма е вики-форматировани",
-       "image_tip": "Чуоттаяь файл",
+       "image_tip": "Чухьнахьара файл",
        "media_tip": "Файлá тIатовжам",
        "sig_tip": "Хьа кулгаяздар а, хӀанзара ха а",
        "hr_tip": "ПхьорагIен така (цох пайда эцар тIехдаьнна кастта ма де)",
        "summary": "Хувцамий сурт оттадар",
        "subject": "Тема/даькъа цIи:",
        "minoredit": "ЗӀамига хувцам",
-       "watchthis": "Зем бе укх оагӀон",
+       "watchthis": "Зем бе укх оагӀонна",
        "savearticle": "ОагӀув дIаязъе",
        "preview": "Хьалххе бIаргтохар",
        "showpreview": "Хьалххе бIаргтохар",
        "loginreqpagetext": "Оаш шоаш $1 деза кхыйола оагIонашка хьожаргдолаш.",
        "accmailtitle": "КъайладIоагӀа дӀадахьийтад",
        "newarticle": "(Kерда)",
-       "newarticletext": "Шо Ñ\82IаÑ\85Ñ\8cожаÑ\8fÑ\80гаÑ\86а Ð´ÐµÑ\85Ñ\8cадаÑ\8cннад Ð¹Ð¾Ð°Ñ\86а Ð¾Ð°Ð³Ó\80он Ñ\82Ó\80а.\nÐ\98з Ñ\85Ñ\8cакÑ\85оллаÑ\80гÑ\8cйолаÑ\88 ÐºÓ\80алÑ\85агÓ\80а Ð´Ð¾Ð°Ð»Ð° ÐºÐ¾Ñ\80аÑ\87Ñ\83 Ñ\82екÑ\81Ñ\82 IоÑ\87Ñ\83Ñ\8fзде (нагаÑ\85Ñ\8cа Ñ\81анна ÐºÑ\85еÑ\82аде Ñ\85ала Ð´Ð°Ð»Ðµ [$1 Ð½Ð¾Ð²ÐºÑ\8aоÑ\81Ñ\82алаÑ\80а Ð¾Ð°Ð³Ó\80онга] Ñ\85Ñ\8cажа).\nЦа Ñ\85овÑ\88 Ñ\83кÑ\85аза Ð½Ð¸Ð¹Ñ\81деннадале, Ñ\88оай Ð±Ñ\80аÑ\83зеÑ\80а '''ЮÑ\85а''' (назад) Ñ\82оIаеÑ\80 тӀа пӀелг тоӀабе.",
-       "anontalkpagetext": "----\n<em>Ер я дагара йоазув кхы а хьакхолланза вовзаш воацача доакъашхочун дувца оттадара оагIув.</em>\nПоэтому мы вынуждены для его/её идентификации использовать цифровой IP-адрес.\nЭтот же адрес может использоваться нескольким другим участникам.\nЕсли вы анонимный участник и полагаете, что получили сообщения, адресованные не вам, пожалуйста, [[Special:CreateAccount|создайте учётную запись]] или [[Special:UserLogin|представьтесь системе]], чтобы впредь избежать возможной путаницы с другими анонимными участниками.",
-       "noarticletext": "ХIанза укх оагӀон тӀа текст яц.\nШун аьттув ба [[Special:Search/{{PAGENAME}}|цу тайпара цӀи хьоаяр кораде]] кхыйола оагIонаш тIа, иштта\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} тара дола тептарий дIаяздаьраш], е\n'''[{{fullurl:{{FULLPAGENAME}}|action=edit}} изза мо цӀи йолаш оагӀув хьакхолла]'''</span>.",
-       "noarticletext-nopermission": "ХIанз укх оагӀон тӀа текст яц.\nШун аьттув ба [[Special:Search/{{PAGENAME}}|цу тайпара цӀи белгалъяр хьалаха]] кхыйола оагIонаш тIа, иштта\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} тара дола тептарай дIаяздаьраш].</span> Ер оагӀув хьакхолла Хьа бокъо яц.",
-       "userpage-userdoesnotexist-view": "«$1» яха дагара йоазув дац.",
+       "newarticletext": "Шо Ñ\82IаÑ\82овжама Ð³Iолла Ð´ÐµÑ\85Ñ\8cадаÑ\8cннад Ð¹Ð¾Ð»Ð°Ñ\88 Ð¹Ð¾Ð°Ñ\86аÑ\87а Ð¾Ð°Ð³Ó\80он Ñ\82Ó\80а.\nÐ\98з Ñ\85Ñ\8cакÑ\85оллаÑ\80гÑ\8cйолаÑ\88 ÐºÓ\80алÑ\85агÓ\80а Ð´Ð¾Ð°Ð»Ð°Ñ\87а ÐºÐ¾Ñ\80аÑ\87Ñ\83 Ñ\82екÑ\81Ñ\82 IоÑ\87Ñ\83Ñ\8fзÑ\8aе (нагаÑ\85Ñ\8cа Ñ\81анна ÐºÑ\85еÑ\82аде Ñ\85ала Ð´Ð°Ð»Ðµ [$1 Ð½Ð¾Ð²ÐºÑ\8aоÑ\81Ñ\82алаÑ\80а Ð¾Ð°Ð³Ó\80онга] Ñ\85Ñ\8cажа).\nЦаÑ\85овÑ\88 Ñ\83кÑ\85аза Ð½Ð¸Ð¹Ñ\81деннадале, Ñ\88оай Ð±Ñ\80аÑ\83зеÑ\80а Ñ\87Ñ\83 '''ЮÑ\85а''' (Ð\9dазад) Ñ\8fÑ\85а Ñ\82оIаеÑ\80а тӀа пӀелг тоӀабе.",
+       "anontalkpagetext": "----\n<em>Ер да вовзаш воацача (ший дагара йоазув кхы а хьакхолланза) доакъашхочун оагIув ювцар.</em>\nЦу бахьане тхо декхарийла да цу сагá идентификаци ер духьа цун IP-цӀай хьахьокха.\nИз цӀай леладеш хила мег масехк кхыболча доакъашхоша а.\nХьо вовзаш воаца доакъашхо вале, ер хоам хьона боагIаш бац аьнна хеташ а вале, дехар да [[Special:CreateAccount|хьакхолла дагара йоазув]] е [[Special:UserLogin|хьавовзийта ражá]], тIехьагIа хье тувлавайтаргвоацаш бовзаш боацача доакъашхошца.",
+       "noarticletext": "ХIанз укх оагӀон тӀа текст яц.\nШун аьттув ба [[Special:Search/{{PAGENAME}}|цу тайпара цӀи хьоахаяр лаха]] кхыйолча оагIонаш тIа, иштта\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} тептарий дIаяздаьраш лаха] е\n'''[{{fullurl:{{FULLPAGENAME}}|action=edit}} иззамо цӀи йолаш оагӀув хьакхолла]'''</span>.",
+       "noarticletext-nopermission": "ХIанз укх оагӀон тӀа текст яц.\nШун аьттув ба кхыйолча оагIонаш тIа [[Special:Search/{{PAGENAME}}|цу тайпара цӀи хьохаяр лаха]], иштта <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} тептарий дIаяздаьраш лаха].</span> Ер оагӀув хьакхолла Хьа бокъо яц.",
+       "userpage-userdoesnotexist-view": "«$1» Ñ\8fÑ\85а Ð´Ð°Ð³Ð°Ñ\80а Ð¹Ð¾Ð°Ð·Ñ\83в Ð´Ð¾Ð»Ð°Ñ\88 Ð´Ð°Ñ\86.",
        "clearyourcache": "<strong>Теркал де.</strong> Хетаргахьа, оагIув дIаязъяь яьлча шоай браузера кэш IоцIанъе езаргья шун, даь хувцамаш гургдолаш.\n* <strong>Firefox / Safari:</strong> <em>Shift</em> яха лак тоIояь лоаттаеш инструментий цхьа дакъа тIа тоIае <em>Обновить</em> е <em>Ctrl-F5</em> тоIае е <em>Ctrl-R</em> (<em>⌘-R</em> Mac тIа)\n* <strong>Google Chrome:</strong> ТоIае <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> Mac тIа)\n* <strong>Internet Explorer:</strong> <em>Ctrl</em> яха лак тоIояь лоаттаеш, тоIае <em>Обновить</em> е <em>Ctrl-F5</em> тоIае\n* <strong>Opera:</strong> ДехьагIо <em>Menu → Настройки</em> (<em>Opera → Настройки</em> Mac тIа), тIаккха <em>Безопасность → Очистить историю посещений → Кэшированные изображения и файлы</em>",
        "note": "'''Белгалдоахар:'''",
        "previewnote": "'''Теркам бе, ер хьалххе бIаргтохар мара бац.'''\nХьа хувцамаш хIанза а дIаяздаь дац!",
        "yourtext": "Хьа текст",
        "copyrightwarning": "Теркам бе, статьяй текста деррига хувцамаш а, тIатохараш а укх лицензи $2 хьалашца (условия) лоархIаш да (хь. $1).\nНагахь санна Шоай тексташ Шун пурам доацаш лоIаме даржийта а, моллагIа волча саго хувца йиш йолаш хилийта а Шун безам беце, уж укхаз (Википейде) ма язде.<br />\nИштта, Оаш бакъду Шоай тIатохама Шо автораш хилар, е из кеп яьккха укхаз хьачудаккхар лоIаме чудар долча хьаста (источник) тIара.\n'''АВТОРСКИ БОКЪОНАШЦА ЛОРАДЕШ ДОЛА МАТЕРИАЛАШ УКХАЗ ЧУ МА ДАХА!'''",
        "templatesused": "Укх оагIон тIа {{PLURAL:$1|1=пайда эца ло|пайда эца лераш}}:",
-       "templatesusedpreview": "БIаргтохара раже {{PLURAL:$1|1=пайда эцаш ло|пайда эцаш лераш}}:",
+       "templatesusedpreview": "БIаргтохар хьалотадича пайда эцаш {{PLURAL:$1|1=лелабеш бола ло|леладеш дола лераш}}:",
        "template-protected": "(лорадаь да)",
        "template-semiprotected": "(цхьа долча даькъе гIо оттадаь да)",
        "hiddencategories": "Ер оагIув {{PLURAL:$1|$1 къайла категориех|1=цаI къайла категорех}} я:",
        "undo-failure": "Ер нийсдар юхадаккха йиш яц юкъе даь хувцамаш бахьане",
        "viewpagelogs": "Укх оагӀон тептараш хьахьокха",
        "currentrev-asof": "ТӀехьара эрш $1",
-       "revisionasof": "Ð\92еÑ\80Ñ\81и $1",
-       "revision-info": "Ð\92еÑ\80Ñ\81и $1; {{GENDER:$6|$2}}$7",
-       "previousrevision": "← Xьалхарча",
-       "nextrevision": "ТIехьайоагIараш →",
+       "revisionasof": "ЭÑ\80Ñ\88 $1",
+       "revision-info": "ЭÑ\80Ñ\88 ($1); {{GENDER:$6|$2}}$7",
+       "previousrevision": "← XьалхайоагIа",
+       "nextrevision": "ТIехьайоагIа →",
        "currentrevisionlink": "ХIанзара верси",
        "cur": "хӀанза.",
        "next": "тӀехь.",
        "last": "хьалха.",
        "page_first": "цхьоаллагIа",
        "page_last": "тӀехьара",
-       "histlegend": "Ð\92еÑ\80Ñ\81ий Ñ\85оÑ\80жам: Ð±ÐµÐ»Ð³Ð°Ð»Ñ\8aе Ñ\88Ñ\83н Ð²IаÑ\88и Ð¹Ð¸Ñ\81Ñ\82а Ð±ÐµÐ·Ð°Ð¼ Ð±Ð¾Ð»Ð° Ð¾Ð°Ð³Iон Ð²ÐµÑ\80Ñ\81еÑ\88, Ñ\82IаккÑ\85а Ñ\82оIае '''{{int:compare-submit}}'''.<br />\nÐ\9aÑ\85еÑ\82аваÑ\80: '''({{int:cur}})''' â\80\94 ÐºÐ°Ñ\80аÑ\80а Ð²ÐµÑ\80Ñ\81еÑ\86а Ð´Ð¾Ð»Ð° Ð±Ð°Ñ\88Ñ\85алонаÑ\88; '''({{int:last}})''' â\80\94 Ñ\85Ñ\8cалÑ\85а Ð¹Ð¾Ð°Ð³IаÑ\88 Ð²ÐµÑ\80Ñ\81еÑ\86а Ð´ола башхалонаш; '''{{int:minoreditletter}}''' — зIамига хувцамаш.",
+       "histlegend": "ЭÑ\80Ñ\88ий Ñ\85оÑ\80жам: Ñ\85Ñ\8cабелгалÑ\8aе Ñ\88Ñ\83н Ð²IаÑ\88и Ð¹Ð¸Ñ\81Ñ\82а Ð±ÐµÐ·Ð°Ð¼ Ð±Ð¾Ð»Ð° Ð¾Ð°Ð³Iон Ñ\8dÑ\80Ñ\88аÑ\88, Ñ\82IаккÑ\85а Ñ\82оIае '''{{int:compare-submit}}'''.<br />\nÐ\9aÑ\85еÑ\82аваÑ\80: '''({{int:cur}})''' â\80\94 ÐºÐ°Ñ\80аÑ\80Ñ\87а Ñ\8dÑ\80Ñ\88аи Ñ\85еÑ\80жаÑ\87а Ñ\8dÑ\80Ñ\88аи Ñ\8eкÑ\8aе Ð¹Ð¾Ð»Ð° Ð±Ð°Ñ\88Ñ\85алонаÑ\88; '''({{int:last}})''' â\80\94 Ñ\85Ñ\8cалÑ\85а Ð¹Ð¾Ð°Ð³IаÑ\87а Ñ\8dÑ\80Ñ\88аи Ñ\85еÑ\80жаÑ\87а Ñ\8dÑ\80Ñ\88аи Ñ\8eкÑ\8aе Ð¹ола башхалонаш; '''{{int:minoreditletter}}''' — зIамига хувцамаш.",
        "history-fieldset-title": "Даь хинна хувцамаш лахар",
-       "history-show-deleted": "Алхха дӀадаьккхараш",
+       "history-show-deleted": "Алхха дӀадаьха хувцамаш",
        "histfirst": "эггара къаьнагIа",
        "histlast": "эггара кердагIа",
        "historyempty": "(яьсса)",
        "history-feed-title": "Хувцамий истори",
-       "history-feed-description": "Укх оагӀон Википейде дола хувцамий истори",
-       "history-feed-item-nocomment": "$1 → укх хан $2",
+       "history-feed-description": "Укх оагӀон хувцамий истори вике чу",
+       "history-feed-item-nocomment": "$1 → укх хáна: $2",
        "rev-delundel": "хьахьокха/къайлаяккха",
        "rev-showdeleted": "хьахьокха",
        "revdelete-show-file-submit": "XӀа-а",
        "searchprofile-advanced": "Шердаь",
        "searchprofile-articles-tooltip": "$1 чу лахар",
        "searchprofile-images-tooltip": "Файлаш лахар",
-       "searchprofile-everything-tooltip": "Массайола оагIонаш тIа лахар (дувцар оттадара оагIонаш чулоацаш)",
-       "searchprofile-advanced-tooltip": "Iочуязаяь цIерий аренашка лаха",
+       "searchprofile-everything-tooltip": "Массайолча оагIонаш тIа лахар (дувцара оагIонаш чу а лоацаш)",
+       "searchprofile-advanced-tooltip": "Лаха хьахержача цIерий аренашка",
        "search-result-size": "$1 ({{PLURAL:$2|$2 дош|$2 дешаш}})",
        "search-result-category-size": "$1 {{PLURAL:$1|юкъедахар}} ($2 {{PLURAL:$2|кIалоагIат}}, $3 {{PLURAL:$3|файл}})",
-       "search-redirect": "(дIа-хьахьожадар $1 тIара)",
+       "search-redirect": "($1 яхача оагIон тIара дIа-хьа хьожадар)",
        "search-section": "(дáкъа «$1»)",
        "search-file-match": "(цхьатара хул файла чударца)",
        "search-suggest": "Хьона эшар ер хила мега: $1",
-       "search-interwiki-caption": "Ð\93аÑ\80гаÑ\80а Ð¿Ñ\80оекÑ\82аÑ\88",
+       "search-interwiki-caption": "Ð\9aÑ\85Ñ\8bйолÑ\87а Ð³Ð°Ñ\80гаÑ\80Ñ\87а Ð¿Ñ\80оекÑ\82аÑ\88ка ÐºÐ¾Ñ\80адаÑ\8cÑ\80",
        "search-interwiki-default": "Хьахиннараш укхазар $1:",
        "search-interwiki-more": "(кхы а)",
        "search-relatedarticle": "ВIашагIдувзаденна",
        "powersearch-togglenone": "Цхьаккха",
        "powersearch-remember": "Дагалáца хержар кхы тӀехьагӀа лохача хана накъадаргдолаш",
        "preferences": "ГIирс тоаяраш",
-       "mypreferences": "Ð\93IиÑ\80Ñ\81аш",
+       "mypreferences": "Ð\9eÑ\82Ñ\82амаш",
        "prefs-skin": "ТIера кийчдара тема",
        "skin-preview": "Хьалххе бIаргтохар",
        "prefs-personal": "Доакъашхочун дараш",
        "prefs-rc": "Керда нийсдараш",
-       "prefs-watchlist": "Зем бара хьаязъяьр",
+       "prefs-watchlist": "Зéма хьаязъяьр",
        "prefs-watchlist-days": "Дéной дукхал:",
        "prefs-resetpass": "Хувца къайладIоагIа",
        "prefs-rendering": "ТIера куц",
        "username": "{{GENDER:$1|Доакъашхочун цӀи}}:",
        "yourrealname": "Бокъонца йола цIи:",
        "yourlanguage": "Мотт:",
-       "gender-male": "ВикиоагIонаш нийсaеш ва из",
-       "gender-female": "ВикиоагIонаш нийсaеш я из",
+       "gender-male": "ВикиоагIонаш тоаеш ва из",
+       "gender-female": "ВикиоагIонаш тоаеш я из",
        "email": "Email",
        "prefs-help-email": "Электронни почта адрес оттаде параз дац, амма из эшаш хургда, нагахьа санна хьона хьа къайладIоагIа дицлой.",
        "prefs-help-email-others": "Иштта цунца кхыболча доакъашхошта аьттув хургба шоаца бувзам бе а, шун оагIон тIа е шун дувца оттадара оагIон тIа йола тIахьожаяргаца.\nШун электронни почта адрес цхьаннена гуш хургъяц.",
        "userrights-user-editname": "Iочуязъе доакъашхочун цӀи:",
        "editusergroup": "Йотта доакъашхой тоабаш",
        "saveusergroups": "ДIаязъе {{GENDER:$1|доакъашхочун}} тоабаш",
-       "userrights-groupsmember": "Дакъа лоаца тоабаш чу:",
+       "userrights-groupsmember": "Дáкъа лоац укх тоабаш чу:",
        "userrights-reason": "Бахьан:",
        "userrights-changeable-col": "Оаш хувца мегаш йола тоабаш",
        "userrights-unchangeable-col": "Хьа хувца йиш йоаца тоабаш",
        "right-createtalk": "дувца оттадара оагӀонаш кхоллар",
        "right-move": "оагIонай цIераш хувцар",
        "right-movefile": "файлай цӀераш хувцар",
-       "right-writeapi": "Ð\94IаÑ\8fздаÑ\80а Ð»Ð°Ñ\8cÑ\80Ñ\85Ñ\85Iа API Ð¿Ð°Ð¹Ð´Ð° Ñ\8dÑ\86аÑ\80",
+       "right-writeapi": "дIаÑ\8fздеÑ\88 Ð»ÐµÐ»Ð°Ðµ API",
        "newuserlogpage": "Доакъашхой дIаязбаь таптар",
        "rightslog": "Доакъашхочун бокъоний тéптар",
        "action-read": "ер оагӀув éшар",
        "nchanges": "$1 {{PLURAL:$1|хувцам}}",
        "enhancedrc-history": "истори",
        "recentchanges": "Керда хувцамаш",
-       "recentchanges-legend": "Ð\9aеÑ\80да Ñ\85Ñ\83вÑ\86амий Ð³IиÑ\80Ñ\81аÑ\88 Ñ\82оаÑ\8fÑ\80аш",
-       "recentchanges-summary": "КIалхагIа ханашца нийсдаь дIаяьздаь да {{grammar:genitive|{{SITENAME}}}}  оагIонай тIеххьара хувцамаш.",
+       "recentchanges-legend": "Ð\9aеÑ\80да Ñ\85Ñ\83вÑ\86амий Ð¾Ñ\82Ñ\82амаш",
+       "recentchanges-summary": "КIалхагIа Iохьоахадаьд Википеден оагIонаш чу даь хувцамаш тIехьардараш лакхе долаш.",
        "recentchanges-noresult": "Белгалъяьча хана цхьаккха хувцамаш даь хинна дац.",
        "recentchanges-feed-description": "Хьéжа укх потоке вики чу тIехьара хувцамашка.",
        "recentchanges-label-newpage": "Укх хувцамаца керда оагIув кхелла хиннай",
        "rc-change-size-new": "Хувцам баьнначул тӀехьагIа бола боарам: $1 {{PLURAL:$1|байт}}",
        "rc-enhanced-expand": "Хьахьокха ма дарра",
        "rc-enhanced-hide": "Къайладаккха ма дарра дар",
-       "rc-old-title": "духхьара кхелла хиннай «$1» цӀи йолаш",
+       "rc-old-title": "юххьанцара кхелла хиннай «$1» цӀи йолаш",
        "recentchangeslinked": "ВIашагIдувзаденна нийсдараш",
        "recentchangeslinked-feed": "ВIашагIдувзаденна нийсдараш",
        "recentchangeslinked-toolbox": "ВIашагIдувзаденна хувцамаш",
        "recentchangeslinked-title": "$1ца вIашидувзаденна хувцамаш",
-       "recentchangeslinked-summary": "Ӏочуязъе оагӀон цӀи, цунна тӀатовжаш йолча оагӀонаш чу даь дола хувцамаш бӀаргагургдолаш (ОагӀата доакъашхой бӀаргагургболаш Ӏочуязъе Category:ОагӀата цӀи). \n[[Special:Watchlist|Хьа зема хьаязъяьра]] юкъейоагӀа оагӀонаш <strong>сомача лапӀазаца</strong> белгалаяьй.",
+       "recentchangeslinked-summary": "Ӏочуязъе оагӀон цӀи, цунна тӀатовжаш йолча оагӀонаш чу даь дола хувцамаш бӀаргагургдолаш (ОагӀата доакъашхой бӀаргагургболаш Ӏочуязъе {{ns:category}}:ОагӀата цӀи).\n[[Special:Watchlist|Хьа зéма хьаязъяьрá]] юкъейоагӀача оагӀонаш чу даь хувцамаш <strong>сомача лапӀазаца</strong> белгалдаь да.",
        "recentchangeslinked-page": "ОагIон цIи",
        "recentchangeslinked-to": "Вешта, белгаляьккха оагIон тIахьожавеш дола оагIонашта даь хувцамаш хьахьокха.",
        "upload": "Файл чуяккха",
        "license": "Лицензи ялар:",
        "license-header": "Лицензировани",
        "imgfile": "файл",
-       "listfiles": "Файлай хьаязъяьр",
-       "listfiles_date": "ТаÑ\8cÑ\80аÑ\85Ñ\8c",
+       "listfiles": "Файлий хьаязъяьр",
+       "listfiles_date": "Ð\94и",
        "listfiles_name": "Файла цӀи",
        "listfiles_user": "Доакъашхо",
        "listfiles_size": "Боарам",
        "listfiles_description": "Йоазонца сурт оттадар",
-       "listfiles_count": "Ð\92еÑ\80Ñ\81и",
+       "listfiles_count": "ЭÑ\80Ñ\88",
        "file-anchor-link": "Файл",
        "filehist": "Файла истори",
-       "filehist-help": "ТаÑ\8cÑ\80аÑ\85Ñ\8c\85а Ñ\82Iа Ñ\82оIабе Ñ\86Ñ\83 Ñ\85ан Ñ\84айл Ð¼Ð¸Ñ\88Ñ\82а Ñ\85иннай Ñ\85Ñ\8cожаpгдолаш",
+       "filehist-help": "Ð\94и/Ñ\85а Ð´Ð¾Ð»Ñ\87а IоÑ\82IаÑ\82оIае Ñ\86Ñ\83 Ñ\85ана Ñ\84айл Ð¼Ð¸Ñ\88Ñ\82а Ñ\85иннай Ñ\85Ñ\8cажа Ð¹Ð¸Ñ\88 Ñ\85Ñ\83pгÑ\8cйолаш",
        "filehist-revert": "юхаяьккха",
        "filehist-current": "xIанзара",
-       "filehist-datetime": "ТаÑ\8cÑ\80аÑ\85Ñ\8c/Ха",
+       "filehist-datetime": "Ð\94и/Ха",
        "filehist-thumb": "ЗIамигасурт",
-       "filehist-thumbtext": "Ð\97Iамига Ñ\81Ñ\83Ñ\80Ñ\82 Ñ\83кÑ\85 Ð²ÐµÑ\80Ñ\81ин $1",
+       "filehist-thumbtext": "Ð\97IамагIа Ð¹Ð¾Ð»Ð° Ñ\8dÑ\80Ñ\88 Ñ\83кÑ\85 Ñ\85ана Ñ\85инна: $1",
        "filehist-nothumb": "Миниатюра яц",
        "filehist-user": "Доакъашхо",
        "filehist-dimensions": "Файла боарам",
        "filehist-comment": "Белгалдаккхар",
        "imagelinks": "Файлах пайда эцар",
        "linkstoimage": "{{PLURAL:$1|1=ТIехьайоагIача $1 оагIо тIахьожаву|ТIехьайоагIача $1 оагIонаш тIахьожаву}} укх файла тIа:",
-       "linkstoimage-more": "$1-ннел дуккхагIа {{PLURAL:$1|оагIув}} я укх файла тIахьожавеш.\nУкх хьаязъяьра чу белгалъяй цу файла {{PLURAL:$1|алхха $1 тIахьожаярг}}.\nТIакхача йиш я иштта [[Special:WhatLinksHere/$2|бIарчча хьаязъяьра]].",
+       "linkstoimage-more": "$1-ннел дуккхагIа {{PLURAL:$1|оагIув}} я укх файлá тIахьожавеш.\nУкх хьаязъяьра чу хьахьекхаб цу файла алхха {{PLURAL:$1|$1 тIатовжам}}.\nЦхьабакъда [[Special:WhatLinksHere/$2|бIарчча хьаязъяьра]] а тIакхача йиш я хьа.",
        "nolinkstoimage": "Укх файла тӏатовжаш оагӏонаш яц.",
        "linkstoimage-redirect": "$1 (файлови дӀа-хьа хьожавар) $2",
        "sharedupload": "Ер файл $1 чура я, из пайда эцаш лелае мегаш я кхыйола проекташ чу.",
        "sharedupload-desc-here": "Ер файл $1 чура я, иштта кхыйола проекташ чу пайда эца аьттув болаш я.\nЦун [$2 сурт оттадара оагIон] хоам кIалхахь хьабоалабаьб.",
        "filepage-nofile": "Ишта цӀи йола файл йоацаш я.",
-       "uploadnewversion-linktext": "ЧÑ\83Ñ\8fккÑ\85а Ñ\83кÑ\85 Ñ\84айла ÐºÐµÑ\80да Ð²ÐµÑ\80Ñ\81и",
+       "uploadnewversion-linktext": "Ð¥Ñ\8cаÑ\87Ñ\83Ñ\8fккÑ\85а Ñ\83кÑ\85 Ñ\84айла ÐºÐµÑ\80да Ñ\8dÑ\80Ñ\88",
        "upload-disallowed-here": "Хьа бокъо яц ер файл юха дӀаязъе.",
        "filerevert-comment": "Бахьан:",
        "filedelete-comment": "Бахьан:",
        "filedelete-reason-otherlist": "Кхыдола бахьан",
        "download": "хьачуяккха",
        "unwatchedpages": "Цхьанне а зем беш йоаца оагIонаш",
-       "randompage": "Ца ховш нийсъенна статья",
-       "statistics": "СÑ\82аÑ\82иÑ\81Ñ\82ика",
+       "randompage": "Цаховш нийсъенна статья",
+       "statistics": "Ð\93Ó\80Ñ\83лакÑ\85\85Ñ\8cал",
        "statistics-articles": "Статьяш",
        "statistics-pages": "ОагIонаш",
-       "double-redirect-fixer": "Ð\94Ó\80а-Ñ\81аÑ\85Ñ\8cожадаÑ\80аÑ\88 Ñ\82оадер",
+       "double-redirect-fixer": "Ð\94Ó\80а-Ñ\85Ñ\8cа Ñ\85Ñ\8cожаваÑ\80а Ð¾Ð°Ð³IонаÑ\88 Ñ\82оаер",
        "brokenredirects-edit": "нийсъе",
        "brokenredirects-delete": "дӀаяккха",
        "withoutinterwiki-submit": "Хьахьокха",
        "nbytes": "$1 {{PLURAL:$1|байт}}",
        "nmembers": "$1 {{PLURAL:$1|объект}}",
        "prefixindex": "ОагӀоний цӀерий дешхьалхех хьахокхар",
+       "prefixindex-namespace": "ОагӀоний цӀераш шоай дешхьалхех хьахьокхар (цIерий моттиг «{{ns:$1}}»)",
        "shortpages": "Лоаца оагIонаш",
        "longpages": "ЙIаьха оагIонаш",
        "protectedpages-page": "ОагIув",
        "movethispage": "ЦIи хувца укх оагIон",
        "pager-newer-n": "$1 дукхагIа {{PLURAL:$1|керда}}",
        "pager-older-n": "{{PLURAL:$1|къаьнара дара|къаьнара дараш|къаьнара долaчарех}} $1",
-       "booksources": "Ð\94жейнай Ñ\85Ñ\8cаÑ\81Ñ\82аÑ\88 (иÑ\81Ñ\82оÑ\87ники)",
+       "booksources": "Ð\9aинижкий Ñ\85Ñ\8cаÑ\81Ñ\82аÑ\88",
        "booksources-search-legend": "Джейнах лаьца хоам лахар",
        "booksources-search": "Хьалáха",
        "specialloguserlabel": "Доакъашхо:",
        "speciallogtitlelabel": "Эшар (цӀи е доакъашхо):",
        "log": "Тептараш",
        "all-logs-page": "Деррига тIакхача йиш йола тептараш",
-       "alllogstext": "{{SITENAME}} Ñ\81айÑ\82а Ñ\82епÑ\82аÑ\80ий Ñ\8eкÑ\8aаÑ\80а Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80.\nÐ¥Ñ\8cа Ð¹Ð¸Ñ\88 Ñ\8f Ñ\85Ñ\8cаÑ\85иннаÑ\80 Ñ\85Ñ\8cаÑ\85аÑ\80жа Ñ\82епÑ\82аÑ\80а Ñ\82айпаÑ\85, Ð´Ð¾Ð°ÐºÑ\8aаÑ\88Ñ\85оÑ\87Ñ\83н Ñ\86IеÑ\80а Ñ\82айпаÑ\85 (Ñ\80егиÑ\81Ñ\82Ñ\80 Ð»Ð¾Ð°Ñ\80Ñ\85IаÑ\88 Ñ\8f) Ðµ Ñ\85Ñ\8cаÑ\85аÑ\8fÑ\8cÑ\87а Ð¾Ð°Ð³Iон Ñ\82айпаÑ\85 (Ñ\80егиÑ\81Ñ\82Ñ\80 Ð»Ð¾Ð°Ñ\80Ñ\85IаÑ\88 Ñ\8f).",
+       "alllogstext": "{{SITENAME}} Ñ\8fÑ\85аÑ\87а Ñ\81айÑ\82а Ñ\82епÑ\82аÑ\80ий Ñ\8eкÑ\8aаÑ\80а Ñ\85Ñ\8cаÑ\8fзÑ\8aÑ\8fÑ\8cÑ\80.\nÐ¥Ñ\8cалеÑ\85аÑ\80аÑ\88 ÐºÑ\8aеÑ\80да Ð¹Ð¸Ñ\88 Ñ\8f Ñ\82епÑ\82аÑ\80а Ñ\82айпаÑ\85, Ð´Ð¾Ð°ÐºÑ\8aаÑ\88Ñ\85оÑ\87Ñ\83н Ñ\86IеÑ\80аÑ\85 (Ñ\80егиÑ\81Ñ\82Ñ\80 Ð»Ð¾Ð°Ñ\80Ñ\85IаÑ\88 Ñ\8f) Ðµ Ñ\85Ñ\8cоаÑ\85аÑ\8fÑ\8cÑ\87а Ð¾Ð°Ð³IонаÑ\85 (Ñ\83кÑ\85аза Ð° Ð¸Ñ\88Ñ\82Ñ\82а Ñ\80егиÑ\81Ñ\82Ñ\80 Ð»Ð¾Ð°Ñ\80Ñ\85I).",
        "logempty": "Укх оагӀон дӀаяздаьраш тептара чу дац.",
        "allpages": "Еррига оагIонаш",
        "prevpage": "Хьалха йоагIа оагIув ($1)",
-       "allpagesfrom": "Ð¥Ñ\8cаÑ\85Ñ\8cокÑ\85а Ð¾Ð°Ð³Ó\80онаÑ\88 дӀайолалуш йола укх алапех:",
+       "allpagesfrom": "Ð\93Ñ\83Ñ\87аÑ\8fÑ\85а Ð¾Ð°Ð³Ó\80онаÑ\88, дӀайолалуш йола укх алапех:",
        "allpagesto": "Хьахьокхар соцадé укхун тӀа:",
        "allarticles": "Еррига оагIонаш",
        "allpagessubmit": "Кхоачашде",
-       "allpages-hide-redirects": "Ð\9aÑ\8aайладаÑ\85а Ð´Ó\80а-Ñ\81аÑ\85Ñ\8cожадараш",
-       "categories": "ОагIаташ",
+       "allpages-hide-redirects": "Ð\94IакÑ\8aайладаÑ\85а Ð´Ó\80а-Ñ\85Ñ\8cа Ñ\85Ñ\8cожавераш",
+       "categories": "ОагӀаташ",
        "linksearch": "Арахьара тIахьожаяргаш лахар",
        "linksearch-ns": "ЦIерий аренаш:",
-       "linksearch-ok": "Хьалáха",
-       "linksearch-line": "$2 — тIахьожаярг укхаз $1",
+       "linksearch-ok": "Хьалаха",
+       "linksearch-line": "$1 яхача оагIонна тIатовжам $2 чура",
        "listgrouprights-members": "(доакъашхой хьаязъяьр)",
        "listgrouprights-namespaceprotection-namespace": "ЦIерий аре",
        "emailuser": "Доакъашхочоа каьхат",
        "watchlist-details": "Хьа зем бара хьаязъяьр чу $1 {{PLURAL:$1|оагIув}} я (иштта дувца оттадара оагIонаш а).",
        "wlheader-showupdated": "Хувцаенна оагIонаш '''сома''' шрифтаца белгалъяьй.",
        "wlnote": "КIалха хьагойт {{PLURAL:$2|тIехьарча сахьата|тIехьарча <strong>$2</strong> сахьата}} даь хинна {{PLURAL:$1|тIеххьара хувцам|тIеххьара <strong>$1</strong> хувцам}} ($3 $4).",
-       "wlshowlast": "Хьахьокха тIехьара $1 сахьатах $2 дийнахь",
+       "wlshowlast": "Хьахьокха тIехьарча $2 ден $1 сахьатá",
        "watchlist-options": "Зем бара хьаязъяьра тоадараш",
        "watching": "Зем бара хьаязъяьр чу тIатохар",
        "unwatching": "Зем бара хьаязъяьр чура дIадаккхар",
        "rollbacklinkcount": "юхататта $1 {{PLURAL:$1|нийсдар}}",
        "protectlogpage": "ГIон тептар",
        "protectedarticle": "Лораяьй оагӀув «[[$1]]»",
-       "modifiedarticleprotection": "Лорадара лагIа хийцад оагIон «[[$1]]»",
+       "modifiedarticleprotection": "Лорадара лагIа хийцад «[[$1]]» яхача оагIон",
        "protectcomment": "Бахьан:",
        "protectexpiry": "Чакхдоала:",
        "protect_expiry_invalid": "Лорадар чакхадоала харцахьа ха",
        "whatlinkshere": "Тӏатовжамаш укхаза",
        "whatlinkshere-title": "«$1» яхача оагӏонна тӏатовжаш йола оагӏонаш",
        "whatlinkshere-page": "ОагIув:",
-       "linkshere": "«'''[[:$1]]'''» яхача оагIонна тIахьожавеш я тIехьайоагIа:",
+       "linkshere": "«'''[[:$1]]'''» ← укхунна тӀахьожавеш я тӀехьайоагӀа оагӀонаш:",
        "nolinkshere": "Кхыйолча оагӏонашкара '''[[:$1]]''' яхача оагӏон тIатовжамаш доацаш да.",
-       "isredirect": "оагIÑ\83в-дIа-Ñ\81аÑ\85Ñ\8cожадаÑ\80",
+       "isredirect": "дIа-Ñ\85Ñ\8cа Ñ\85Ñ\8cожаваÑ\80а Ð¾Ð°Ð³IÑ\83в",
        "istemplate": "юкъейоалаяр",
        "isimage": "Файлови тӏатовжам",
        "whatlinkshere-prev": "{{PLURAL:$1|1=хьалхайоагIа|хьалхайоагIараш}} $1",
        "whatlinkshere-hideredirs": "$1 дӀа-хьа хьожавараш",
        "whatlinkshere-hidetrans": "$1 юкъедоаладаьраш",
        "whatlinkshere-hidelinks": "$1 тӏатовжамаш",
-       "whatlinkshere-hideimages": "$1 Ñ\84айлай Ñ\82IаÑ\85Ñ\8cожаÑ\8fÑ\80гаш",
+       "whatlinkshere-hideimages": "$1 Ñ\84айлай Ñ\82IаÑ\82овжамаш",
        "whatlinkshere-filters": "Фильтраш",
        "blockip": "ЧIега тоха {{GENDER:$1|доакъашхочун}}",
        "ipboptions": "2 сахьат:2 hours,1 ди:1 day,3 ди:3 days,1 кIира:1 week,2 кIира:2 weeks,1 бутт:1 month,3 бутт:3 months,6 бутт:6 months,1 шу:1 year,хоадаяь ха йоаца:infinite",
        "importlogpage": "Импорта тептар",
        "tooltip-pt-userpage": "{{GENDER:|Хьа}} доакъашхочун оагIув",
        "tooltip-pt-mytalk": "{{GENDER:|Хьа}} дувца оттадара оагIув",
-       "tooltip-pt-preferences": "{{GENDER:|Ð¥Ñ\8cа Ð³IиÑ\80Ñ\81аш}}",
+       "tooltip-pt-preferences": "{{GENDER:|Ð¥Ñ\8cа Ð¾Ñ\82Ñ\82амаш}}",
        "tooltip-pt-watchlist": "Iа зем бу оагIонаш",
        "tooltip-pt-mycontris": "{{GENDER:|хьа}} хувцамаш",
        "tooltip-pt-login": "Укхаза хьай цIи аьле чувала/яла йиша я, амма из параз дац",
        "tooltip-pt-logout": "Аравала/яла",
-       "tooltip-pt-createaccount": "Хьа бокъо я дагара йоазув кхелла система чувала, амма параз долаш дац из.",
-       "tooltip-ca-talk": "Ð\9eагIон Ñ\87Ñ\83даÑ\80 Ð´Ñ\83вÑ\86а Ð¾Ñ\82Ñ\82адар",
+       "tooltip-pt-createaccount": "Хьа бокъо я дагара йоазув хьа а кхелла ражача чувала.",
+       "tooltip-ca-talk": "Ð\9eагIон Ñ\87Ñ\83лоаÑ\86амаÑ\85 Ð»Ð°Ñ\8cÑ\86а Ð´Ñ\83вÑ\86ар",
        "tooltip-ca-edit": "Нийсъе ер оагIув",
        "tooltip-ca-addsection": "Керда дáкъа хьаде",
-       "tooltip-ca-viewsource": "Ер оагIув хувцамбарах гIо теха (лорая) я, амма цунна дIадолалу текстага хьажа а, из тIерхьаязъе а бокъо я.",
+       "tooltip-ca-viewsource": "Ер оагIув хувца йиш хургьйоацаш лораяь я, амма цун чухьнахьарча текстага хьажа а из тIера хьаязъе а вIаштехьа да.",
        "tooltip-ca-history": "Укх оагIон даь хувцамаш тIа дола тептар",
        "tooltip-ca-protect": "Лорае ер оагIув хувцамаш дергдоацаш",
        "tooltip-ca-delete": "ДӀаяккха ер оагӀув",
        "tooltip-ca-move": "Укх оагӀон цӀи хувца",
-       "tooltip-ca-watch": "ТIатоха ер оагIув хьа зем бара хьаязъяьра",
+       "tooltip-ca-watch": "ТIатоха ер оагIув хьай зéма хьаязъяьрá",
        "tooltip-ca-unwatch": "ДӀаяккха ер оагӀув шоай зема хьаязъяьра тIара",
        "tooltip-search": "Хьалáха {{grammar:prepositional|{{SITENAME}}}} чу",
        "tooltip-search-go": "Изза мо цӀи йолаш оагӀон тӀa дехьавала",
        "tooltip-n-randompage": "Башхало йоаца ца ховш нийсъенна оагӀув хьаела",
        "tooltip-n-help": "Новкъостал лаха мегаш йола моттиг",
        "tooltip-t-whatlinkshere": "Укхаза тӏатовжаш йола оагӏонаш",
-       "tooltip-t-recentchangeslinked": "УкÑ\85 Ð¾Ð°Ð³Iо Ñ\82IаÑ\85Ñ\8cожавеÑ\88 Ð¹Ð¾Ð»Ñ\87а Ð¾Ð°Ð³Iонай Ñ\82IеÑ\85хьара хувцамаш",
+       "tooltip-t-recentchangeslinked": "Ð\95Ñ\80 Ð¾Ð°Ð³IÑ\83в Ñ\82IаÑ\82овжаÑ\88 Ð¹Ð¾Ð»Ñ\87а Ð¾Ð°Ð³IонаÑ\88 Ñ\87Ñ\83Ñ\80а Ñ\82Iехьара хувцамаш",
        "tooltip-feed-rss": "RSS чу гойтар укх оагIон",
        "tooltip-feed-atom": "Укх оагIонна лаьрххIа Atom чу трансляци яр",
        "tooltip-t-contributions": "{{GENDER:$1|Укх доакъашхочо хийца}} йола оагIонаш",
-       "tooltip-t-emailuser": "ДIахьийта каьхат {{GENDER:$1|укх доакъашхочун}}",
+       "tooltip-t-emailuser": "ДIадахьийта каьхат {{GENDER:$1|укх доакъашхочунга}}",
        "tooltip-t-upload": "Файлаш чуяккха",
        "tooltip-t-specialpages": "ГIулакха оагIонаш",
        "tooltip-t-print": "Укх оагӏон зарба тохара эрш",
        "tooltip-ca-nstab-mediawiki": "MediaWiki хоамбара оагIув",
        "tooltip-ca-nstab-template": "Лера оагIув",
        "tooltip-ca-nstab-help": "Новкъостала оагIув",
-       "tooltip-ca-nstab-category": "ОагIата оагӀув",
+       "tooltip-ca-nstab-category": "ОагӀата оагӀув",
        "tooltip-minoredit": "Ер хувцар кIезига дар санна белгалде",
        "tooltip-save": "Хьа хувцамаш лорадеш дIаязде",
-       "tooltip-preview": "Ð\94еÑ\85аÑ\80 Ð´Ð°, Ð¾Ð°Ð³Ó\80Ñ\83в Ð»Ð¾Ñ\80аеÑ\88Ñ\8c Ð´IаÑ\8fзÑ\8aелеÑ\85Ñ\8c Ð¸Ð· Ð¼Ð¸Ñ\88Ñ\82а Ñ\8f Ñ\82аÑ\85ка Ñ\85Ñ\8cалÑ\85Ñ\85е Ð±IаÑ\80гÑ\82оÑ\85аÑ\80аÑ\85 Ð¿Ð°Ð¹Ð´Ð° Ñ\8dÑ\86аÑ\88!",
-       "tooltip-diff": "Ð\94IадолалÑ\83 текстаца даь хувцамаш хьахьокха",
+       "tooltip-preview": "Ð\9eагIонна Ñ\85Ñ\8cалÑ\85Ñ\85е Ð±IаÑ\80гÑ\82оÑ\85аÑ\80.\nÐ\94еÑ\85аÑ\80 Ð´Ð°, Ð¾Ð°Ð³Ó\80Ñ\83в Ð´IаÑ\8fзÑ\8aелеÑ\85Ñ\8c Ñ\85Ñ\8cажа Ð¸Ð· Ð¼Ð¸Ñ\88Ñ\82а Ñ\8f!",
+       "tooltip-diff": "ЧÑ\83Ñ\85Ñ\8cнаÑ\85Ñ\8cаÑ\80а текстаца даь хувцамаш хьахьокха",
        "tooltip-compareselectedversions": "Укх оагIон хержа шин версешта юкъе йола башхалога хьажа.",
        "tooltip-watch": "ТIатоха ер оагIув хьа зем бара хьаязъяьра",
-       "tooltip-rollback": "ЦкÑ\8aа Ð¿Iелг Ñ\82оIабе Ð´IадаккÑ\85а Ñ\82IеÑ\85Ñ\8cаÑ\80а Ñ\80едакÑ\82оÑ\80аÑ\81 даь хувцамаш",
+       "tooltip-rollback": "ЦкÑ\8aа Ð¿Iелг Ñ\82оIабаÑ\8c Ð´IадаÑ\85а Ñ\82IеÑ\85Ñ\8cаÑ\80Ñ\87а Ñ\80едакÑ\82оÑ\80о даь хувцамаш",
        "tooltip-undo": "Даь хувцар дIадаьккха, хьалххе бIаргтохар хьахьокха, дIадаккхара бахьан Iочуязде аьттув болаш.",
        "tooltip-summary": "Лоаца сурт оттадар Iочуязде",
        "simpleantispam-label": "Анти-спам тахкар.\n<strong>Цхьа</strong> хIама ма язъе укхаз!",
        "pageinfo-robot-noindex": "Могадаьдац",
        "pageinfo-watchers": "Зем беш болчар дуккхал",
        "pageinfo-few-watchers": "{{PLURAL:$1|Зем бер}} $1-ннел кIезигагIа ба",
-       "pageinfo-redirects-name": "УкÑ\85 Ð¾Ð°Ð³Ó\80онна Ð´Ó\80а-Ñ\81аÑ\85Ñ\8cожадарий дуккхал",
+       "pageinfo-redirects-name": "УкÑ\85 Ð¾Ð°Ð³Ó\80онна Ð´Ó\80а-Ñ\85Ñ\8cа Ñ\85Ñ\8cожадаÑ\8cрий дуккхал",
        "pageinfo-subpages-name": "Укх оагӀон кIалоагӀонаш",
-       "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|дIа-Ñ\81аÑ\85Ñ\8cожадаÑ\80}}; $3 {{PLURAL:$3|обÑ\8bÑ\87наÑ\8f|обÑ\8bÑ\87нÑ\8bе|обÑ\8bÑ\87нÑ\8bÑ\85}})",
+       "pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|дIа-Ñ\85Ñ\8cа Ñ\85Ñ\8cожаÑ\8fÑ\8cÑ\80}}; $3 {{PLURAL:$3|кÑ\85Ñ\8bÑ\8fÑ\80}})",
        "pageinfo-firstuser": "ОагӀув кхеллар",
-       "pageinfo-firsttime": "ОагӀув кхелла хинна таьрахь",
+       "pageinfo-firsttime": "ОагӀув кхелла хинна ди",
        "pageinfo-lastuser": "ТӀехьара хувцам баьр",
        "pageinfo-lasttime": "ТӀехьара нийсдар даь хинна таьрахь",
        "pageinfo-edits": "Дерригача нийсдарий дукхал",
        "pageinfo-recent-edits": "ТӀехьарча хана даь нийсдар (укх хана юкъе: $1)",
        "pageinfo-recent-authors": "ТӀехьарча хана бола башха автораш",
        "pageinfo-magic-words": "{{PLURAL:$1|1=Тамашийна дош|Тамашийна дешаш}} ($1)",
-       "pageinfo-hidden-categories": "{{PLURAL:$1|1=Къайла оагIат}} ($1)",
+       "pageinfo-hidden-categories": "{{PLURAL:$1|Къайла оагӀат|Къайла оагӀаташ}} ($1)",
        "pageinfo-templates": "{{PLURAL:$1|1=Ло|Лераш}} ($1)",
        "pageinfo-toolboxlink": "ОагIонах бола хоам",
        "pageinfo-contentpage": "Счётчико чулацаме оагIув санна лоархI",
        "pageinfo-contentpage-yes": "XIаа",
        "patrol-log-page": "ТӀахьожама тептар",
-       "previousdiff": "â\86\90 Ð¥Ñ\8cалÑ\85аÑ\80а Ð½Ð¸Ð¹Ñ\81дар",
-       "nextdiff": "ТIайоагIа Ð½Ð¸Ð¹Ñ\81Ñ\8aаÑ\80",
+       "previousdiff": "â\86\90 Ð\9aÑ\8aаÑ\8cнагIа Ð´Ð¾Ð»Ð° Ð½Ð¸Ð¹Ñ\81даÑ\8cр",
+       "nextdiff": "Ð\9aеÑ\80дагÓ\80а Ð´Ð¾Ð»Ð° Ð½Ð¸Ð¹Ñ\81даÑ\8cÑ\80 â\86\92",
        "widthheightpage": "$1 × $2, $3 {{PLURAL:$3|оагӀув}}",
        "file-info-size": "$1 × $2 {{PLURAL:$2|пиксель}}, файла боарам: $3, MIME-тайпа: $4",
        "file-info-size-pages": "$1 × $2 пиксель, файлан боарам: $3, MIME-тайп: $4, $5 {{PLURAL:$5|1=оагӀув}}",
-       "file-nohires": "УкÑ\85ал Ð´Ñ\83ккÑ\85агIа Ð´Ð¾ÐºÐºÑ\85ал Ð´Ð¾Ð»Ð°Ñ\88 Ð²ÐµÑ\80Ñ\81и Ñ\8fÑ\86",
+       "file-nohires": "Ð\9aÑ\85Ñ\8b Ð¹Ð¾ÐºÐºÑ\85агIа Ñ\8dÑ\80Ñ\88 Ñ\8fÑ\86.",
        "svg-long-desc": "SVG-файл, номинально $1 × $2 {{PLURAL:$2|пиксель}}, файлан боарам: $3",
-       "show-big-image": "Ð\94IадолалÑ\83 файл",
-       "show-big-image-preview": "Ð\91оаÑ\80ам Ñ\85Ñ\8cалÑ\85Ñ\85е Ð±IаÑ\80гÑ\82оÑ\85аÑ\87 Ñ\85ан: $1.",
-       "show-big-image-other": "{{PLURAL:$2|1=Кхыбола тIера боарам|Кхыбола тIера боарам}}: $1.",
+       "show-big-image": "Ð\9eÑ\80игиналÑ\8cни файл",
+       "show-big-image-preview": "Ð¥Ñ\8cалÑ\85Ñ\85е Ð±IаÑ\80гÑ\82оÑ\85аÑ\87а Ñ\85ана Ð±Ð¾Ð»Ð° Ð±Ð¾Ð°Ñ\80ам: $1.",
+       "show-big-image-other": "{{PLURAL:$2|1=Кхыбола тIера боарам}}: $1.",
        "show-big-image-size": "$1 × $2 пиксель",
        "noimages": "Суртaш дац.",
-       "ilsubmit": "Хьалáха",
-       "bad_image_list": "Формат хила еза иштта:\n\nЛоархIаш хургда алхха хьаязъяьра элементаш (укх * бехкама белгалонаца долалуш дола могIараш).\nМогIара цхьоаллагIа тIахьожаярг чуоттаде мегаш доаца сурта тIахьожавеш хила еза.\nЦу могIара тIехьайоагIа тIахьожаяргаш лоархIаш хургья эргамаш (исключения) санна, вешта аьлча, сурт чуоттаде мегаш йола статьяш санна.",
+       "ilsubmit": "Хьалаха",
+       "bad_image_list": "Формат хила езаш я иштта:\n\nЛоархIаш хургда алхха хьаязъяьра элементаш (укх * яхача хьаракаца дIадолалуш дола могIараш).\nМогIара хьалхара тIатовжам хила безаш ба хьачудаккха йиш йоацача суртá тIатовжаш.\nАмма цун тIехьадоагIа тIатовжамаш тIатовжаш хургда сурт чуоттаде йиш йолча статьяшта.",
        "metadata": "Мета-дараш",
-       "metadata-help": "Файло кхыдола дараш чулоаца, цифровой суртдоакхарго е сканеро тIатохаш дола. Нагахьа файл чуякхачул тIехьа хийца хинна дале, цхьаццайола параметраш хIанзара сурта тIара йоацаш хила мегаш я.",
+       "metadata-help": "Файло кхыдола дараш чулоац, цифрацара суртдоакхарго е сканеро тIатохаш дола. Нагахьа санна файл хьачуякхачул тIехьагIа хийца хинна яле, цхьаццайола параметраш хIанзарча сурта тIа йоацаш хила мег.",
        "metadata-expand": "Хьахьокха кхыдола дараш",
-       "metadata-collapse": "Ð\9aÑ\8aайладаккха кхыдола дараш",
-       "metadata-fields": "Укх хьаяьзъяра чу дагaрадаь суртий метахоамий йистош, хьахьекха хургда сурта оагIон тIа, хьоарчадаь метахоамий ильг долаш. Юхедиса йистош къайла хургда.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
+       "metadata-collapse": "Ð\94IакÑ\8aайладаха кхыдола дараш",
+       "metadata-fields": "Укх хьаязъяьра чу Iохьоахаяь сурта метадарий йистош хьахьекха хургья сурта оагIон тIа дIахьулъяьча метадарий таблица чу. Юхейиса йистош къайла хургья.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
        "exif-imagewidth": "Шерал",
        "exif-imagelength": "Лакхал",
        "exif-orientation": "Сурта белгало",
        "exif-xresolution": "ПхьорагIа тIера боарам",
        "exif-yresolution": "УрагIа тIера боарам",
-       "exif-datetime": "Файл хийца хинна таьрахьи хаи",
+       "exif-datetime": "Файл хийца хинна дии хаи",
        "exif-imagedescription": "Сурта цIи",
        "exif-make": "Камера кийчъяь арахийцар",
        "exif-model": "Камера модель",
        "exif-datetimedigitized": "Оцифровк яь таьрахь а, ха а",
        "exif-writer": "Текста автор",
        "exif-languagecode": "Мотт",
-       "exif-iimcategory": "Ð\9aаÑ\82егоÑ\80и",
-       "exif-orientation-1": "гIаÑ\8cÑ\85Ñ\8cа",
+       "exif-iimcategory": "Ð\9eагÓ\80аÑ\82",
+       "exif-orientation-1": "Ð\9bеÑ\80Ñ\82Ó\8fа",
        "exif-exposureprogram-1": "Кара",
        "exif-scenecapturetype-1": "Ландшафт",
        "exif-scenecapturetype-2": "Сага сурт",
        "watchlisttools-raw": "Массаза йола текст санна хувца",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|дувца оттадар]])",
        "duplicate-defaultsort": "Теркам. Долча тайпара дIанийсдара дIоагIа «$2» юхакъоастаду долча тайпара дIанийсдара хьалха хинна дIоагIа «$1».",
-       "version": "Ð\92еÑ\80Ñ\81и",
+       "version": "ЭÑ\80Ñ\88",
        "version-specialpages": "ГIулакха оагӀонаш",
        "version-version": "($1)",
-       "version-software-version": "Ð\92еÑ\80Ñ\81и",
-       "redirect": "Файла идентификатора тIара, доакъашхочун тIара, оагIон тIара, версин е тептара тIара дIа-сахьожадар",
-       "redirect-summary": "Укх белха оагIо дIа-сахьожаву файла (файлан цIера тIара), оагIонна (оагIон тIара е оагIон эрша идентификатора тIара), доакъашхочун оагIонна (доакъашхочун таьрахьа идентификатора тIара) е тептара йоазонна (тептара идентификатора тIара). Пайда эцар: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] или [[{{#Special:Redirect}}/logid/186]].",
+       "version-software-version": "ЭÑ\80Ñ\88",
+       "redirect": "Файла идентификатора тIара, доакъашхочун тIара, оагIон тIара, эрша тIара е тептара тIара дIа-хьа хьожавар",
+       "redirect-summary": "Укх белха оагIоно дIа-хьа хьожаву файлá (файла цIера тIара), оагIонна (оагIон тIара е оагIон эрша идентификатора тIара), доакъашхочун оагIонна (доакъашхочун таьрахьа идентификатора тIара) е тептара йоазонна (тептара идентификатора тIара). Пайда эцар: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], [[{{#Special:Redirect}}/user/101]] или [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Дехьавала",
        "redirect-lookup": "Лахар:",
        "redirect-value": "Боарам:",
        "specialpages": "ЛаьрххIа йола оагIонаш",
        "specialpages-group-users": "Доакъашхойи бокъонаши",
        "specialpages-group-pages": "ОагIонай хьаязъяьраш",
-       "specialpages-group-pagetools": "ОагIонашта дола гIирсаш",
+       "specialpages-group-pagetools": "ОагIонашта эша кечалаш",
        "external_image_whitelist": "#Ер мугI ший болча тайпара бита<pre>\n#Укхаз оттаде кастта дувлача выражений фрагменташ (// юкъе дола дакъа)\n#арахьара суртий URL адресашца дIанийсалургда уш.\n#Мегаргдола сурташ санна хьахьекха хургда, дIаходараш, сурташта тIахьожаяргаш санна хьахьекха хургда.\n#Укханца # долалуш дола могIараш алараш санна лоархIаш да.\n#МогIараш регистраца кIаьда дац\n\n#Укх могIара лакхе оттаде кастта дувлача выражений фрагменташ. Ер мугI ший болча тайпара бита</pre>",
        "tag-filter": "[[Special:Tags|Белгалонай]] фильтр:",
        "tag-filter-submit": "Литта",
        "tags-title": "Белгалонаш",
        "tags-tag": "Белгалон цӀи",
        "tags-hitcount-header": "Белгалдаь нийсдараш",
-       "tags-active-yes": "XIаа",
+       "tags-active-yes": "XӀау",
        "tags-active-no": "A",
        "tags-edit": "нийсде",
        "tags-hitcount": "$1 {{PLURAL:$1|1=хувцам|хувцамаш}}",
        "tags-create-submit": "Хьакхолла",
        "compare-page1": "ЦхьоаллагIа оагIув",
        "compare-page2": "ШоллагIа оагӀув",
-       "compare-rev1": "ЦÑ\85Ñ\8cоаллагIа Ð²ÐµÑ\80Ñ\81и",
-       "compare-rev2": "ШоллагӀа верси",
+       "compare-rev1": "Ð¥Ñ\8cалÑ\85аÑ\80а Ñ\8dÑ\80Ñ\88",
+       "compare-rev2": "ШоллагӀа эрш",
        "htmlform-submit": "ДIадахьийта",
        "htmlform-reset": "Хувцамаш юхадаккха",
        "htmlform-selectorother-other": "Кхыдар",
        "logentry-newusers-create": "{{GENDER:$2|Доакъашхочо хьакхеллад}} дагара йоазув $1",
        "logentry-newusers-autocreate": "Ше-ше кхеллай {{GENDER:$2|доакъашхочун}} $1 дагара йоазув",
        "logentry-upload-upload": "$1 {{GENDER:$2|чуяьккхай}} $3",
-       "logentry-upload-overwrite": "$1 доакъашхочо {{GENDER:$2|чуяьккхай}} керда верси $3",
+       "logentry-upload-overwrite": "$1 доакъашхочо {{GENDER:$2|чуяьккхай}} $3 яхачун керда эрш",
        "rightsnone": "(яц)",
        "searchsuggest-search": "Хьалаха {{grammar:prepositional|{{SITENAME}}}} чу",
        "duration-days": "{{PLURAL:$1|ди}}",
        "expand_templates_preview": "Хьалххе бIаргтохар",
        "pagelang-name": "ОагIув",
-       "special-characters-group-latin": "Ð\9bаÑ\82иной",
+       "special-characters-group-latin": "Ð\9bаÑ\82иний",
        "special-characters-group-greek": "Эллиной",
        "special-characters-group-cyrillic": "Кириллица",
        "special-characters-group-arabic": "Iарбий",
index 0e23e41..8032223 100644 (file)
        "preview": "Previdar",
        "showpreview": "Previdar",
        "showdiff": "Montrez chanji",
-       "blankarticle": "<strong>Averto:</strong> La pagino vu kreas es vakua.\nSe vu ri-selektos \"$1\", la pagino kreesos sen irga kontenajo.",
+       "blankarticle": "<strong>Averto:</strong> La pagino quon vu kreis es vakua.\nSe vu ri-selektos \"$1\", la pagino kreesos sen irga kontenajo.",
        "anoneditwarning": "<strong>Averto:</strong> Vu ne eniris.\nVua IP-adreso esos videbla publike se vu redaktos. Se vu <strong>[$1 enirus]</strong> od <strong>[$2 kreus konto]</strong>, vua redakti atribuesos a vua uzeronomo, kune kun altra bonaji.",
        "anonpreviewwarning": "<em>Vu ne eniris. Konservar chanji registragos vua IP-adreso en la redakto-historio di ta pagino.</em>",
        "missingcommenttext": "Voluntez skribar komento.",
        "permissionserrorstext-withaction": "Vu ne darfas $2, pro la {{PLURAL:$1|kauzo|kauzi}} sequanta:",
        "recreate-moveddeleted-warn": "<strong>Atencez: Vu rikreos pagino qua antee efacesis.</strong>\n\nVu mustas konsiderar se esos konvenanta o ne riskribor ol.\nPor vua konoco, la motivo dil antea efaco montresas hike:",
        "moveddeleted-notice": "Ica pagino efacesis.\nL'efaco-registraro e la movo-registraro di la pagino povas videsar sequante, por konsulto.",
+       "moveddeleted-notice-recent": "Pardonez, ica pagino efacesis recente (dum la lasta 24 hori).\nL'informo (log) pri l'efaco, la protektado e/o movo di la pagino povas videsar adinfre, por konsulto.",
        "log-fulllog": "Videz kompleta protokolo ('log')",
        "edit-conflict": "Konflikto di editi.",
        "postedit-confirmation-created": "La pagino kreesis.",
        "rcfilters-days-title": "Recenta dii",
        "rcfilters-hours-title": "Recenta hori",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|dio|dii}}",
+       "rcfilters-days-show-hours": "$1 {{PLURAL:$1|horo|hori}}",
        "rcfilters-quickfilters": "Konservita filtrili",
        "rcfilters-quickfilters-placeholder-title": "Nula filtrilo konservesis til nun",
        "rcfilters-savedqueries-defaultlabel": "Konservita filtrili",
        "filesource": "Fonto:",
        "ignorewarning": "Ignorar la averto e gardar la arkivo irgakaze.",
        "badfilename": "La imajo-nomo chanjesis a \"$1\".",
+       "empty-file": "L'arkivo sendita da vu esas vakua.",
        "fileexists": "Arkivo kun ta nomo ja existas.\nVolutez kontrolar <strong>[[:$1]]</strong> se {{GENDER:|vu}} ne esas certa pri chanjar olu.\n[[$1|thumb]]",
        "filepageexists": "La pagino kun deskripto pri ica arkivo ja kreesis en <strong>[[:$1]]</strong>, tamen nul arkivo kun ica nomo existas ankore.\nLa rezumo pri ol quon vu skriptis ne aparos en la deskripto-pagino.\nPor ke la rezumo aparos ibe, vu mustos <strong>skribor ol manuale.</strong>\n[[$1|thumb]]",
        "uploadwarning": "Averto pri la adkargo di arkivo",
        "withoutinterwiki": "Pagini sen linguo-ligili",
        "withoutinterwiki-legend": "Prefixo",
        "withoutinterwiki-submit": "Montrar",
+       "fewestrevisions": "Pagini kun poka revizi",
        "nbytes": "$1 {{PLURAL:$1|bicoko|bicoki}}",
        "ncategories": "$1 {{PLURAL:$1|kategorio|kategorii}}",
        "nlinks": "$1 {{PLURAL:$1|ligilo|ligili}}",
        "booksources-search-legend": "Serchez librala fonti",
        "booksources-search": "Serchar",
        "booksources-text": "Infre vu povas vidar listo di ligili ad altra retsitui qui vendas nova ed uzata libri, ed anke povas havar informi pri la libri quin vu serchabas:\nLa {{SITENAME}} ne mantenas komercala relati kun ta vendeyi mencionata, e la listo ne povas konsideresar rekomendo o vend-anunco.",
+       "magiclink-tracking-pmid-desc": "Ica pagino uzas magiala ligili PMID. Videz [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org] pri quale migrar.",
        "magiclink-tracking-isbn": "Pagini qui uzas ligili ISBN",
        "specialloguserlabel": "Agero:",
        "speciallogtitlelabel": "Skopo (titulo od {{ns:user}}:uzernomo por uzero):",
        "listgrouprights": "Permisi dil grupo di uzeri",
        "listgrouprights-group": "Grupo",
        "listgrouprights-members": "(listo di membri)",
+       "trackingcategories": "Kategorii por kontrolo",
+       "trackingcategories-name": "Nomo di la mesajo",
+       "trackingcategories-desc": "Kriterii por inkluzar kategorii",
        "restricted-displaytitle-ignored-desc": "La pagino havas nekonocita titulo <code><nowiki>{{DISPLAYTITLE}}</nowiki></code>, pro ol ne esas equivalanta a la nuna titulo di ica pagino.",
        "mailnologin": "Ne sendar adreso",
        "mailnologintext": "Vu mustas [[Special:UserLogin|enirir]] e havar valida e-adreso en vua [[Special:Preferences|preferaji]] por sendar e-posto ad altra uzanti.",
        "protect-othertime-op": "altra tempo",
        "protect-otherreason": "Altra/suplementala motivo:",
        "protect-otherreason-op": "Altra motivo",
+       "protect-dropdown": "*Frequa motivi por protektado\n** Intensa vandalismo\n** Intensa atako per 'spam'\n** Redakto-milito neutila\n** Pagino multe vizitata",
        "protect-expiry-options": "1 horo:1 hour,1 dio:1 day,1 semano:1 week,2 semani:2 weeks,1 monato:1 month,3 monati:3 months,6 monati:6 months,1 yaro:1 year,nefinita:infinite",
        "restriction-type": "Permiso:",
        "pagesize": "(bicoki)",
        "newimages-legend": "Filtrilo",
        "ilsubmit": "Serchar",
        "bydate": "per dato",
+       "hours": "{{PLURAL:$1|$1 horo|$1 hori}}",
        "days": "{{PLURAL:$1|$1 dio|$1 dii}}",
        "weeks": "{{PLURAL:$1|$1 semano|$1 semani}}",
        "months": "{{PLURAL:$1|$1 monato|$1 monati}}",
        "logentry-patrol-patrol-auto": "$1 automatale {{GENDER:$2|indikis}} ke la revizo $4 de la pagino $3 surveyesas",
        "logentry-newusers-create": "La konto dil uzero $1 kreesis.",
        "logentry-newusers-autocreate": "L'uzanto $1 {{GENDER:$2|kreesis}} automatale",
+       "logentry-protect-protect": "$1 protektis la pagino $3 $4",
        "logentry-protect-modify": "$1 {{GENDER:$2|modifikis}} la nivelo di protekto por $3 $4",
        "logentry-protect-modify-cascade": "$1 {{GENDER:$2|modifikis}} la nivelo di protekto di $3 $4 [kaskade]",
        "logentry-upload-upload": "$1 {{GENDER:$2|uploaded}} $3",
index def2cf2..0b18272 100644 (file)
                        "Yiyi",
                        "Manvydasz",
                        "S4b1nuz E.656",
-                       "Daimona Eaytoy"
+                       "Daimona Eaytoy",
+                       "Sarah Bernabei"
                ]
        },
        "tog-underline": "Sottolinea i collegamenti:",
        "prefs-dateformat": "Formato data",
        "prefs-timeoffset": "Ore di differenza",
        "prefs-advancedediting": "Opzioni generali",
+       "prefs-developertools": "Strumenti per gli sviluppatori",
        "prefs-editor": "Editore",
        "prefs-preview": "Anteprima",
        "prefs-advancedrc": "Opzioni avanzate",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "Non verificate",
        "rcfilters-filter-reviewstatus-manual-description": "Modifiche contrassegnate manualmente come verificate.",
        "rcfilters-filter-reviewstatus-manual-label": "Verificato manualmente",
+       "rcfilters-filter-reviewstatus-auto-description": "Le modifiche degli utenti esperti saranno automaticamente marcate come verificate.",
        "rcfilters-filter-reviewstatus-auto-label": "Autoverificato",
        "rcfilters-filtergroup-significance": "Significato",
        "rcfilters-filter-minor-label": "Modifiche minori",
        "recentchangeslinked-feed": "Modifiche correlate",
        "recentchangeslinked-toolbox": "Modifiche correlate",
        "recentchangeslinked-title": "Modifiche correlate a \"$1\"",
-       "recentchangeslinked-summary": "Inserisci il nome di una pagina per vedere le modifiche alle pagine che sono collegate o che collegano a quella pagine. (Per vedere i membri una categoria, inserisci Categoria:Nome della categoria). Le modifiche alle pagine contenute nella propria lista degli [[Special:Watchlist|osservati speciali]] sono evidenziate in <strong>grassetto</strong>.",
+       "recentchangeslinked-summary": "Inserisci il nome di una pagina per vedere le modifiche alle pagine che sono collegate o che collegano a quella pagine. (Per vedere i membri una categoria, inserisci {{ns:category}}:Nome della categoria). Le modifiche alle pagine contenute nella propria lista degli [[Special:Watchlist|osservati speciali]] sono evidenziate in <strong>grassetto</strong>.",
        "recentchangeslinked-page": "Nome della pagina:",
        "recentchangeslinked-to": "Mostra solo le modifiche alle pagine collegate a quella specificata",
        "recentchanges-page-added-to-category": "[[:$1]] aggiunta alla categoria",
        "version-specialpages": "Pagine speciali",
        "version-parserhooks": "Hook del parser",
        "version-variables": "Variabili",
+       "version-editors": "Editori",
        "version-antispam": "Prevenzione dello spam",
        "version-other": "Altro",
        "version-mediahandlers": "Gestori di contenuti multimediali",
index 00dbfe6..5cf1530 100644 (file)
        "prefs-dateformat": "日付と時刻の形式",
        "prefs-timeoffset": "時差",
        "prefs-advancedediting": "全般オプション",
+       "prefs-developertools": "開発者用ツール",
        "prefs-editor": "エディター",
        "prefs-preview": "プレビュー",
        "prefs-advancedrc": "詳細の設定",
index 0159b28..4110c28 100644 (file)
        "prefs-dateformat": "날짜 형식",
        "prefs-timeoffset": "시차 설정",
        "prefs-advancedediting": "일반 옵션",
+       "prefs-developertools": "개발자 도구",
        "prefs-editor": "편집기",
        "prefs-preview": "미리 보기",
        "prefs-advancedrc": "고급 옵션",
        "recentchangeslinked-feed": "가리키는 글의 최근 바뀜",
        "recentchangeslinked-toolbox": "가리키는 글의 최근 바뀜",
        "recentchangeslinked-title": "\"$1\" 문서에 관련된 문서 바뀜",
-       "recentchangeslinked-summary": "해당 문서에 연결된 문서의 변경사항을 확인하려면 문서 이름을 입력하십시오. (분류에 들어있는 문서를 보려면 분류:분류명으로 입력하십시오). [[Special:Watchlist|내 주시문서 목록]]에 있는 문서의 변경사항은 <strong>굵게</strong> 나타납니다.",
+       "recentchangeslinked-summary": "해당 문서에 연결된 문서의 변경사항을 확인하려면 문서 이름을 입력하십시오. (분류에 들어있는 문서를 보려면 {{ns:category}}:분류명으로 입력하십시오). [[Special:Watchlist|내 주시문서 목록]]에 있는 문서의 변경사항은 <strong>굵게</strong> 나타납니다.",
        "recentchangeslinked-page": "문서 이름:",
        "recentchangeslinked-to": "해당 문서를 가리키는 문서의 최근 바뀜 보기",
        "recentchanges-page-added-to-category": "[[:$1]]에 분류를 추가하였습니다",
index 1a0a837..5ef25bf 100644 (file)
        "savearticle": "Sjlaon pagina op",
        "savechanges": "Verangeringe opsjlaon",
        "publishpage": "Pagina publicere",
-       "publishchanges": "Verangeringe publicere",
+       "publishchanges": "Sjlaon verangeringe op",
        "savearticle-start": "Sjlaon pagina op...",
        "savechanges-start": "Sjlaon verangeringe op...",
        "publishpage-start": "Bring pagina oet...",
        "listduplicatedfiles": "Lies mit bestenj mit duplikaote",
        "listduplicatedfiles-summary": "Dit is 'n lies mit bestenj wovan de litste versie e duplikaot is van de recènste versie van 'n anger bestandj. Allein weurt gerapporteerd euver lokaal bestenj.",
        "listduplicatedfiles-entry": "[[:File:$1|$1]] haet [[$3|{{PLURAL:$2|ei duplikaot|$2 duplikaote}}]].",
-       "unusedtemplates": "Óngerbroekde sjablone",
+       "unusedtemplates": "Óngebroekde sjablone",
        "unusedtemplatestext": "Deze pagina guf alle pagina's weer in de {{nas:template}}naamruumde die op gein inkele pagina gebroek waere. Vergaet neet de \"Links nao deze pagina\" te controlere veures dit sjabloon te wösse.",
        "unusedtemplateswlh": "anger links",
        "randompage": "Willekäörige pagina",
        "tooltip-ca-nstab-category": "Betrach de categoriepagina",
        "tooltip-minoredit": "Markeer dit es 'n klein verangering",
        "tooltip-save": "Bewaar dien verangeringe",
-       "tooltip-publish": "Verangeringe publicere",
+       "tooltip-publish": "Sjlaon dien verangeringe op",
        "tooltip-preview": "Betrach dien verangeringe veurdets te ze definitief opsjleis!",
        "tooltip-diff": "Betrach dien verangeringe in de teks.",
        "tooltip-compareselectedversions": "Betrach de versjille tösje de twie geselecteerde versies van dees pagina.",
        "confirm-purge-title": "Vernuuj dees pagina",
        "confirm_purge_button": "ok",
        "confirm-purge-top": "Wils te de buffer vaan dees paas wisse?",
-       "confirm-purge-bottom": "t Opsjone van de cache zorg drveur det de lèste versie van n pagina wörd weergegaeve.",
+       "confirm-purge-bottom": "'t Opsjeune van de cache zorg d'rveur det de lèste versie van 'n pagina weurt getuind.",
        "confirm-watch-button": "Ok",
        "confirm-watch-top": "Dees pagina bie dien volglies zètte?",
        "confirm-unwatch-button": "Ok",
        "unlinkaccounts": "Óntlink konto's",
        "unlinkaccounts-success": "De konto is óntlink wore.",
        "authenticationdatachange-ignored": "De verangering van de authenticatiegegaeves is neet aafgehanjeld wore. Mesjiens is geinen aanbejer ingestèld?",
+       "userjsispublic": "Lit op: JavaScrip-deilpagina's mótte gein vertroewelike gegaeves bevatte ómdet ze kónne waere bekeke door anger gebroekers.",
+       "userjsonispublic": "Lit op: JSON-deilpagina's mótte gein vertroewelike gegaeves bevatte ómdet ze kónne waere bekeke door anger gebroekers.",
+       "usercssispublic": "Lit op: CSS-deilpagina's mótte gein vertroewelike gegaeves bevatte ómdet ze kónne waere bekeke door anger gebroekers.",
+       "restrictionsfield-badip": "Óngeljig IP-adres of -rits: $1",
+       "restrictionsfield-label": "Toegestangde IP-ritse:",
+       "restrictionsfield-help": "Ein IP-adres of CIDR-bereik per lien. Veur alles toe te staon, gebroek:<pre>0.0.0.0/0\n::/0</pre>",
        "edit-error-short": "Fout: $1",
        "edit-error-long": "Foute:\n\n$1",
        "revid": "versie $1",
        "pageid": "paginanómmer $1",
+       "rawhtml-notallowed": "&lt;html&gt; tags kónne allein op normaal pagina's waere geplaats.",
        "gotointerwiki": "{{SITENAME}} verlaote",
        "gotointerwiki-invalid": "De opgegaove titel is óngeljig.",
+       "gotointerwiki-external": "Doe steis op 't puntj {{SITENAME}} te verlaote en [[$2]] te bezeuke. [[$2]] is 'n anger website.\n\n'''[$1 Gank door nao $1]'''",
        "undelete-cantedit": "Doe kans dees pagina neet trögkplaatse ómdet se gein rechte höbs veur dees pagina te bewirke.",
        "undelete-cantcreate": "Doe kans dees pagina neet trögkplaatse ómdet gein bestäönde pagina mit deze naam besteit en doe höbs gein rechte veur dees pagina aan te make.",
        "pagedata-title": "Paginagegaeves",
index 022064c..5bd4131 100644 (file)
        "savechanges": "Išsaugoti pakeitimus",
        "publishpage": "Išsaugoti puslapį",
        "publishchanges": "Išsaugoti pakeitimus",
+       "savearticle-start": "Išsaugoti puslapį…",
+       "savechanges-start": "Išsaugoti pakeitimus…",
+       "publishpage-start": "Išsaugoti puslapį…",
+       "publishchanges-start": "Išsaugoti pakeitimus…",
        "preview": "Peržiūra",
        "showpreview": "Rodyti peržiūrą",
        "showdiff": "Rodyti skirtumus",
        "previewnote": "<strong>Nepamirškite, kad tai tik peržiūra, pakeitimai dar nėra išsaugoti!</strong>",
        "continue-editing": "Eiti į redagavimo sritį",
        "previewconflict": "Ši peržiūra parodo tekstą iš viršutiniojo teksto redagavimo lauko taip, kaip jis bus rodomas, jei pasirinksite išsaugoti.",
-       "session_fail_preview": "'''Atsiprašome! Mes negalime vykdyti jūsų keitimo dėl sesijos duomenų praradimo.\nPrašome pamėginti vėl. Jei tai nepadeda, pamėginkite atsijungti ir prisijungti atgal.'''",
+       "session_fail_preview": "Atsiprašome! Mes negalime vykdyti jūsų keitimo dėl sesijos duomenų praradimo.\n\nGali būti, kad esate atsijungęs. <strong>Prašome patikrinti, ar vis dar esate prisijungęs, ir pabandyti iš naujo</strong>. \nJei ir toliau nepavyksta, pamėginkite [[Special:UserLogout|atsijungti]] ir vėl prisijungti, taip pat patikrinkite, ar jūsų naršyklė priima šios svetainės slapukus.",
        "session_fail_preview_html": "Atsiprašome! Mes negalime apdoroti jūsų keitimo dėl sesijos duomenų praradimo.\n\n<em>Kadangi {{SITENAME}} grynasis HTML yra įjungtas, peržiūra yra paslėpta kaip atsargumo priemonė prieš JavaScript atakas.</em>\n\n<strong>Jei tai teisėtas keitimo bandymas, prašome pamėginti vėl.</strong> Jei tai nepadeda, pamėginkite [[Special:UserLogout|atsijungti]] ir prisijungti atgal, ir patikrinkite ar jūsų naršyklė leidžia šiam tinklapiui naudoti slapukus.",
        "token_suffix_mismatch": "'''Jūsų pakeitimas buvo atmestas, nes jūsų naršyklė iškraipė skyrybos ženklus keitimo žymėje. Keitimas buvo atmestas norint apsaugoti puslapio tekstą nuo sugadinimo. Taip kartais būna, kai jūs naudojate anoniminį tarpinio serverio paslaugą.'''",
        "edit_form_incomplete": "'''Kai redaguoti formos dalys nepasiekė serverio; du kartus patikrinti, kad jūsų pakeitimai yra nesugadintos ir bandykite dar kartą.'''",
        "undo-summary": "Atšauktas [[Special:Contributions/$2|$2]] ([[User talk:$2|Aptarimas]] | [[Special:Contributions/$2|{{MediaWiki:Contribslink}}]]) keitimas ($1 versija)",
        "undo-summary-username-hidden": "Atmesti versiją $1, atliktą paslėpto naudotojo",
        "cantcreateaccount-text": "Paskyrų kūrimą iš šio IP adreso ('''$1''') užblokavo [[User:$3|$3]].\n\n$3 nurodyta priežastis yra ''$2''",
-       "cantcreateaccount-range-text": "Naudotojas [[User:$3|$3]] nustatė draudimą kurti paskyras iš IP adresų plotmės <strong>$1</strong>, į kurią patenka ir jūsiškis IP adresas (<strong>$4</strong>).",
+       "cantcreateaccount-range-text": "Naudotojas [[User:$3|$3]] nustatė draudimą kurti paskyras iš IP adresų plotmės <strong>$1</strong>, į kurią patenka ir jūsiškis IP adresas (<strong>$4</strong>).\n\n$3 nurodyta priežastis yra <em>$2</em>",
        "viewpagelogs": "Rodyti šio puslapio specialiuosius veiksmus",
        "nohistory": "Šis puslapis neturi keitimų istorijos.",
        "currentrev": "Dabartinė versija",
        "rollback-success": "Atmesti {{GENDER:$3|$1}} pakeitimai;\ngrąžinta prieš tai buvusi {{GENDER:$4|$2}} versija.",
        "rollback-success-notify": "Atmesti $1 pakeitimai;\ngrąžinta prieš tai buvusi $2 versija. [$3 Rodyti skirtumus]",
        "sessionfailure-title": "Sesijos klaida",
-       "sessionfailure": "Atrodo yra problemų su jūsų prisijungimo sesija; šis veiksmas buvo atšauktas kaip atsargumo priemonė prieš sesijos vogimą.\nPrašome paspausti „atgal“ ir perkraukite puslapį iš kurio atėjote, ir pamėginkite vėl.",
+       "sessionfailure": "Atrodo yra problemų su jūsų prisijungimo sesija; šis veiksmas buvo atšauktas kaip atsargumo priemonė prieš sesijos vogimą.\nPrašome iš naujo pateikti formą.",
        "changecontentmodel": "Keisti puslapio turinio modelį",
        "changecontentmodel-legend": "Keisti turinio modelį",
        "changecontentmodel-title-label": "Puslapio pavadinimas",
        "log-action-filter-managetags-deactivate": "Žymės deaktyvavimas",
        "log-action-filter-move-move": "Perkėlimai, nepakeičiant nukreipimų",
        "log-action-filter-move-move_redir": "Perkėlimai, pakeičiant buvusius nukreipimus",
+       "log-action-filter-newusers-create": "Sukurta anoniminio naudotojo",
+       "log-action-filter-newusers-create2": "Sukurta registruoto naudotojo",
        "log-action-filter-newusers-autocreate": "Automatinis kūrimas",
+       "log-action-filter-newusers-byemail": "Sukurta su slaptažodžiu, atsiųstu el. paštu",
        "log-action-filter-patrol-patrol": "„Rankinis“ patikrinimas",
        "log-action-filter-patrol-autopatrol": "Automatinis patikrinimas",
        "log-action-filter-protect-protect": "Apsauga",
index 8ac3996..17faeee 100644 (file)
        "rcfilters-filter-minor-description": "Labojumi, kas atzīmēti kā maznozīmīgi.",
        "rcfilters-filter-major-label": "Nozīmīgi labojumi",
        "rcfilters-filter-major-description": "Labojumi, kas nav atzīmēti kā maznozīmīgi.",
+       "rcfilters-filter-watchlist-watchednew-description": "Izmaiņas uzraugāmajās lapās, kuras nav apmeklētas kopš izmaiņu veikšanas.",
        "rcfilters-filter-watchlistactivity-unseen-label": "Neapskatītas izmaiņas",
+       "rcfilters-filter-watchlistactivity-unseen-description": "Izmaiņas lapās, kuras nav apmeklētas kopš izmaiņu veikšanas.",
        "rcfilters-filter-watchlistactivity-seen-label": "Apskatītas izmaiņas",
+       "rcfilters-filter-watchlistactivity-seen-description": "Izmaiņas lapās, kuras ir apmeklētas kopš izmaiņu veikšanas.",
        "rcfilters-filtergroup-changetype": "Izmaiņu veids",
        "rcfilters-filter-pageedits-label": "Lapu labojumi",
        "rcfilters-filter-pageedits-description": "Labojumi vikivietnes saturā, diskusijā, kategoriju aprakstos...",
        "rcfilters-liveupdates-button-title-off": "Rādīt jaunās izmaiņas, tiklīdz tās tiek veiktas",
        "rcfilters-watchlist-markseen-button": "Atzīmēt visas izmaiņas kā apskatītas",
        "rcfilters-watchlist-edit-watchlist-button": "Labot manu uzraugāmo lapu sarakstu",
+       "rcfilters-watchlist-showupdated": "Izmaiņas lapās, kuras nav apmeklētas kopš izmaiņu veikšanas, ir <strong>trekninātā rakstā</strong>.",
        "rcfilters-preference-label": "Paslēpt uzlaboto pēdējo izmaiņu versiju",
        "rcnotefrom": "Zemāk {{PLURAL:$5|redzamas izmaiņas|redzama izmaiņa|redzamas izmaiņas}} kopš <strong>$3, $4</strong> (parādītas ne vairāk kā <strong>$1</strong>).",
        "rclistfromreset": "Atiestatīt datuma izvēli",
        "version-skins": "Uzstādītās apdares",
        "version-specialpages": "Īpašās lapas",
        "version-variables": "Mainīgie",
+       "version-editors": "Redaktori",
        "version-antispam": "Spama aizsardzība",
        "version-other": "Cita",
        "version-hooks": "Aizķeres",
        "htmlform-cloner-create": "Pievienot vairāk",
        "htmlform-cloner-delete": "Noņemt",
        "htmlform-date-placeholder": "GGGG-MM-DD",
+       "htmlform-title-not-creatable": "\"$1\" nav izveidojams lapas nosaukums",
        "htmlform-title-not-exists": "$1 nepastāv.",
        "htmlform-user-not-exists": "<strong>$1</strong> nepastāv.",
        "htmlform-user-not-valid": "<strong>$1</strong> nav derīgs lietotājvārds.",
        "limitreport-templateargumentsize": "Veidnes argumenta izmērs",
        "limitreport-templateargumentsize-value": "$1/$2 {{PLURAL:$2|baiti|baits|baiti}}",
        "limitreport-expensivefunctioncount": "Dārgo parsētāja funkciju skaits",
+       "limitreport-unstrip-size-value": "$1/$2 {{PLURAL:$2|baiti|baits|baiti}}",
+       "expandtemplates": "Izvērst veidnes",
        "expand_templates_output": "Rezultāts",
        "expand_templates_ok": "Labi",
        "expand_templates_remove_nowiki": "Cenzēt <nowiki> iezīmes rezultātā",
        "mediastatistics-header-video": "Video",
        "mediastatistics-header-total": "Visi faili",
        "json-error-syntax": "Sintakses kļūda",
+       "headline-anchor-title": "Saite uz šo sadaļu",
        "special-characters-group-latin": "Latīņu",
        "special-characters-group-latinextended": "Latīņu (papildus)",
        "special-characters-group-ipa": "IPA",
index f2831d1..c694116 100644 (file)
        "viewsourceold": "察源碼",
        "editlink": "纂",
        "viewsourcelink": "察源碼",
-       "editsectionhint": "纂段:$1",
+       "editsectionhint": "所纂章名:$1",
        "toc": "章",
        "showtoc": "示",
        "hidetoc": "藏",
        "searchrelated": "關",
        "searchall": "全",
        "showingresults": "見'''$1'''尋,自'''$2'''始:",
-       "search-nonefound": "詢中無結。",
+       "search-nonefound": "無所獲。",
        "powersearch-legend": "尋",
        "powersearch-ns": "尋名集:",
        "powersearch-togglelabel": "核:",
        "newpageletter": "新",
        "boteditletter": "僕",
        "number_of_watching_users_pageview": "[放有$1哨]",
-       "rc-change-size-new": "既纂,本文有$1字節",
+       "rc-change-size-new": "纂後有$1字節",
        "newsectionsummary": "/* $1 */ 新節",
        "rc-enhanced-expand": "示細",
        "rc-enhanced-hide": "藏細",
index 12f0158..62ccc4f 100644 (file)
@@ -10,7 +10,8 @@
                        "VoteITP",
                        "아라",
                        "Macofe",
-                       "Fitoschido"
+                       "Fitoschido",
+                       "Baloch Khan"
                ]
        },
        "tog-underline": "Garih bawahi tautan:",
        "october-date": "$1 Oktober",
        "november-date": "$1 Nopember",
        "december-date": "$1 Desember",
+       "period-am": "سهار",
+       "period-pm": "ماښام",
        "pagecategories": "{{PLURAL:$1|Kategori}}",
        "category_header": "Laman pado kategori \"$1\"",
        "subcategories": "Subkategori",
        "nospecialpagetext": "<strong>Sanak mamintak laman istimewa nan indak sah.</strong>\n\nDaftar laman istimewa nan sah dapek dicaliak di [[Special:SpecialPages|{{int:specialpages}}]].",
        "error": "Kasalahan",
        "databaseerror": "Kasalahan basis data",
+       "databaseerror-error": "تېروتنه: $1",
        "laggedslavemode": "Paringatan: Laman mungkin indak barisi parubahan tabaru.",
        "readonly": "Basis data dikunci",
        "enterlockreason": "Masuakkan alasan panguncian, tamasuak pakiraan bilo kunci akan dibuka",
        "createacct-reason": "Alasan",
        "createacct-reason-ph": "Manga Sanak mambuek akun lain",
        "createacct-submit": "Buek akun Sanak",
+       "createacct-another-submit": "ګڼون جوړول",
        "createacct-benefit-heading": "{{SITENAME}} dibuek dek urang-urang saroman Sanak.",
        "createacct-benefit-body1": "{{PLURAL:$1|suntiangan}}",
        "createacct-benefit-body2": "{{PLURAL:$1|laman}}",
index 90dd740..b76a4f1 100644 (file)
        "download": "преземи",
        "unwatchedpages": "Ненабљудувани страници",
        "listredirects": "Список на пренасочувања",
-       "listduplicatedfiles": "СпиÑ\81ок Ð½Ð° Ð¿Ð¾Ð´Ð°Ñ\82оÑ\82еки Ñ\81о Ð´Ñ\83пликаÑ\82и",
+       "listduplicatedfiles": "СпиÑ\81ок Ð½Ð° Ð´Ñ\83плиÑ\80ани Ð¿Ð¾Ð´Ð°Ñ\82оÑ\82еки",
        "listduplicatedfiles-summary": "Ова е список на податотеки чија најнова верзија е дупликат на најнова верзија на некоја друга податотека. Се земаат предвид само месни податотеки.",
        "listduplicatedfiles-entry": "[[:File:$1|$1]] има [[$3|{{PLURAL:$2|дупликат|$2 дупликати}}]].",
        "unusedtemplates": "Неискористени шаблони",
        "mostinterwikis": "Страници со најмногу меѓупроектни",
        "mostrevisions": "Статии со најмногу верзии",
        "prefixindex": "Сите страници (со претставка)",
-       "prefixindex-namespace": "Сите страници со претставка (именски простор $1)",
+       "prefixindex-namespace": "Сите страници со претставка (именски простор „$1“)",
        "prefixindex-submit": "Прикажи",
        "prefixindex-strip": "Отстрани ја претставката во списокот",
        "shortpages": "Кратки страници",
        "version-libraries-license": "Лиценца",
        "version-libraries-description": "Опис",
        "version-libraries-authors": "Автори",
-       "redirect": "Ð\9fÑ\80енаÑ\81оÑ\87Ñ\83ваÑ\9aе Ð¿Ð¾ Ð¿Ð¾Ð´Ð°Ñ\82оÑ\82ека, Ñ\81Ñ\82Ñ\80аниÑ\86а, Ð¿Ñ\80еÑ\80абоÑ\82ка Ð¸Ð»Ð¸ Ð½Ð°Ð·Ð½Ð°ÐºÐ° Ð²Ð¾ Ð´Ð½ÐµÐ²Ð½Ð¸ÐºÐ¾Ñ\82",
+       "redirect": "Ð\9fÑ\80енаÑ\81оÑ\87Ñ\83ваÑ\9aе Ð¿Ð¾ Ð½Ð°Ð·Ð½Ð°ÐºÐ° Ð½Ð° Ð¿Ð¾Ð´Ð°Ñ\82оÑ\82ека, ÐºÐ¾Ñ\80иÑ\81ник, Ñ\81Ñ\82Ñ\80аниÑ\86а, Ð¿Ñ\80еÑ\80абоÑ\82ка Ð¸Ð»Ð¸ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº",
        "redirect-summary": "Оваа службена страница пренасочува кон податотека (се задава името), страница (се задава назнаката на преработката или страницата), корисничка странца (се задава бројчената назнака на корисникот) или дневнички запис (се дава назнака на записот). Употреба: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]],  [[{{#Special:Redirect}}/user/101]] или [[{{#Special:Redirect}}/logid/186]].",
        "redirect-submit": "Дај",
        "redirect-lookup": "Пребарај:",
index 393621a..bfd785d 100644 (file)
        "changeemail-no-info": "ഈ താൾ നേരിട്ടു കാണുന്നതിന് താങ്കൾ ലോഗിൻ ചെയ്തിരിക്കണം.",
        "changeemail-oldemail": "ഇപ്പോഴത്തെ ഇമെയിൽ വിലാസം:",
        "changeemail-newemail": "പുതിയ ഇമെയിൽ വിലാസം:",
-       "changeemail-newemail-help": "താà´\99àµ\8dà´\95ൾ à´\87à´®àµ\86യിൽ à´µà´¿à´²à´¾à´¸à´\82 à´¨àµ\80à´\95àµ\8dà´\95à´\82à´\9aàµ\86à´¯àµ\8dയാനാà´\97àµ\8dരഹിà´\95àµ\8dà´\95àµ\81à´¨àµ\8dà´¨àµ\81à´µàµ\86à´\99àµ\8dà´\95ിൽ à´\88 à´®à´£àµ\8dà´¡à´²à´\82 à´\92à´´à´¿à´\9aàµ\8dà´\9aà´¿à´\9fàµ\81à´\95. à´\87à´®àµ\86യിൽ à´µà´¿à´²à´¾à´¸à´\82 à´¨àµ\80à´\95àµ\8dà´\95à´\82à´\9aàµ\86à´¯àµ\8dതാൽ à´ªà´¿à´¨àµ\8dà´¨àµ\86 à´®à´±à´¨àµ\8dà´¨àµ\8d à´ªàµ\8bà´¯ à´°à´¹à´¸àµ\8dയവാà´\95àµ\8dà´\95àµ\8d à´ªàµ\81à´¨à´\83à´¸à´\9càµ\8dà´\9càµ\80à´\95à´°à´¿à´\95àµ\8dà´\95ാനàµ\8b à´\88 à´µà´¿à´\95àµ\8dà´\95ിയിൽ à´¨à´¿à´¨àµ\8dà´¨àµ\81à´³àµ\8dà´³ à´\87à´®àµ\86യിലàµ\81à´\95ൾ à´¸àµ\8dà´µàµ\80à´\95à´°à´¿à´\95àµ\8dà´\95ാനàµ\8b à´\95ഴിയില്ല.",
+       "changeemail-newemail-help": "താà´\99àµ\8dà´\95ൾ à´\87à´®àµ\86യിൽ à´µà´¿à´²à´¾à´¸à´\82 à´¨àµ\80à´\95àµ\8dà´\95à´\82à´\9aàµ\86à´¯àµ\8dയാൻ à´\86à´\97àµ\8dരഹിà´\95àµ\8dà´\95àµ\81à´¨àµ\8dà´¨àµ\81à´µàµ\86à´\99àµ\8dà´\95ിൽ à´\88 à´®à´£àµ\8dà´¡à´²à´\82 à´\92à´´à´¿à´\9aàµ\8dà´\9aà´¿à´\9fàµ\81à´\95. à´\87à´®àµ\86യിൽ à´µà´¿à´²à´¾à´¸à´\82 à´¨àµ\80à´\95àµ\8dà´\95à´\82à´\9aàµ\86à´¯àµ\8dതാൽ à´ªà´¿à´¨àµ\8dà´¨àµ\86 à´®à´±à´¨àµ\8dà´¨àµ\8d à´ªàµ\8bà´¯ à´°à´¹à´¸àµ\8dയവാà´\95àµ\8dà´\95àµ\8d à´ªàµ\81à´¨à´\83à´¸à´\9càµ\8dà´\9càµ\80à´\95à´°à´¿à´\95àµ\8dà´\95ാനàµ\8b à´\88 à´µà´¿à´\95àµ\8dà´\95ിയിൽ à´¨à´¿à´¨àµ\8dà´¨àµ\81à´³àµ\8dà´³ à´\87à´®àµ\86യിലàµ\81à´\95ൾ à´¸àµ\8dà´µàµ\80à´\95à´°à´¿à´\95àµ\8dà´\95ാനàµ\8b à´\95à´´à´¿à´¯àµ\81à´¨àµ\8dനതല്ല.",
        "changeemail-none": "(ഒന്നുമില്ല)",
        "changeemail-password": "താങ്കളുടെ {{SITENAME}} രഹസ്യവാക്ക്:",
        "changeemail-submit": "ഇമെയിലിൽ മാറ്റംവരുത്തുക",
        "recentchanges-legend": "സമീപകാല മാറ്റങ്ങളുടെ ക്രമീകരണം",
        "recentchanges-summary": "{{SITENAME}} സംരംഭത്തിലെ ഏറ്റവും പുതിയ മാറ്റങ്ങൾ ഇവിടെ കാണാം.",
        "recentchanges-noresult": "തന്നിരിക്കുന്ന സമയത്തിനുള്ളിൽ ഇതുമായി പൊരുത്തപ്പെടുന്ന മാറ്റങ്ങൾ ഒന്നുമില്ല.",
+       "recentchanges-timeout": "ഈ തിരച്ചിലിന്റെ സമയം അവസാനിച്ചു. വ്യത്യസ്ത ചരങ്ങൾ ഉപയോഗിച്ച് പരീക്ഷിക്കാവുന്നതാണ്.",
+       "recentchanges-network": "ഒരു സാങ്കേതിക പിഴവുണ്ടായതിനാൽ, ഫലങ്ങളൊന്നും എടുക്കാൻ കഴിഞ്ഞില്ല. ദയവായി താൾ റിഫ്രഷ് ചെയ്ത് നോക്കുക.",
+       "recentchanges-notargetpage": "ഒരു താളുമായി ബന്ധപ്പെട്ട മാറ്റങ്ങൾ കാണുവാൻ താളിന്റെ പേര് നൽകുക.",
        "recentchanges-feed-description": "ഈ ഫീഡ് ഉപയോഗിച്ച് വിക്കിയിലെ പുതിയ മാറ്റങ്ങൾ നിരീക്ഷിക്കുക.",
        "recentchanges-label-newpage": "ഒരു പുതിയ താൾ സൃഷ്ടിച്ചിരിക്കുന്നു",
        "recentchanges-label-minor": "ഇതൊരു ചെറിയ തിരുത്താണ്",
        "recentchangeslinked-feed": "അനുബന്ധ മാറ്റങ്ങൾ",
        "recentchangeslinked-toolbox": "അനുബന്ധ മാറ്റങ്ങൾ",
        "recentchangeslinked-title": "$1 എന്ന താളുമായി ബന്ധപ്പെട്ട മാറ്റങ്ങൾ",
-       "recentchangeslinked-summary": "ഒരു പ്രത്യേക താളിൽ നിന്നു കണ്ണി ചേർക്കപ്പെട്ടിട്ടുള്ള താളുകളിൽ അവസാനമായി വരുത്തിയ മാറ്റങ്ങളുടെ പട്ടിക താഴെ പ്രദർശിപ്പിച്ചിരിക്കുന്നു. ഈ പട്ടികയിൽ പെടുന്ന [[Special:Watchlist|താങ്കൾ ശ്രദ്ധിക്കുന്ന താളുകൾ]] '''കടുപ്പിച്ച്''' കാണിച്ചിരിക്കുന്നു.",
+       "recentchangeslinked-summary": "ഒരു പ്രത്യേക താളിൽ നിന്നു കണ്ണി ചേർക്കപ്പെട്ടിട്ടുള്ള താളുകളിൽ അവസാനമായി വരുത്തിയ മാറ്റങ്ങളുടെ പട്ടിക താഴെ പ്രദർശിപ്പിച്ചിരിക്കുന്നു. (ഒരു വർഗ്ഗത്തിലെ താളുകൾ കാണാൻ {{ns:category}}:വർഗ്ഗത്തിന്റെ പേര് എന്ന് നൽകുക) ഈ പട്ടികയിൽ പെടുന്ന [[Special:Watchlist|താങ്കൾ ശ്രദ്ധിക്കുന്ന താളുകൾ]] '''കടുപ്പിച്ച്''' കാണിച്ചിരിക്കുന്നു.",
        "recentchangeslinked-page": "താളിന്റെ പേര്:",
        "recentchangeslinked-to": "തന്നിരിക്കുന്ന താളിലെ മാറ്റങ്ങൾക്കു പകരം ബന്ധപ്പെട്ട താളുകളിലെ മാറ്റങ്ങൾ കാണിക്കുക",
        "recentchanges-page-added-to-category": "[[:$1]] വർഗ്ഗത്തിലേക്ക് ചേർത്തിരിക്കുന്നു",
        "uploadstash-refresh": "പ്രമാണങ്ങളുടെ പട്ടിക പുതുക്കുക",
        "uploadstash-thumbnail": "ലഘുചിത്രം കാണുക",
        "uploadstash-bad-path-unknown-type": "അപരിചിതമായ തരം \"$1\".",
+       "uploadstash-file-not-found-no-thumb": "ലഘുചിത്രം സംഘടിപ്പിക്കാൻ കഴിഞ്ഞില്ല.",
+       "uploadstash-file-not-found-no-remote-thumb": "ലഘുചിത്രം എടുക്കൽ പരാജയപ്പെട്ടു: $1\nയു.ആർ.എൽ.= $2",
        "img-auth-accessdenied": "പ്രവേശനമില്ല",
        "img-auth-nopathinfo": "PATH_INFO ലഭ്യമല്ല.\nതാങ്കളുടെ സെർവർ ഈ വിവരം കൈമാറ്റം ചെയ്യാൻ തയ്യാറാക്കിയിട്ടില്ല.\nഅത് img_auth പിന്തുണയില്ലാത്ത സി.ജി.ഐ. അധിഷ്ഠിതമായ ഒന്നായിരിക്കാം.\nhttps://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization കാണുക.",
        "img-auth-notindir": "ആവശ്യപ്പെട്ട പാത അപ്‌‌ലോഡ് ഡയറക്റ്ററിയിൽ സജ്ജീകരിച്ചു നൽകിയിട്ടില്ല.",
        "filedelete-edit-reasonlist": "മായ്ക്കലിന്റെ കാരണം തിരുത്തുക",
        "filedelete-maintenance": "നന്നാക്കൽ പ്രവർത്തനങ്ങൾ പുരോഗമിക്കുന്നതിനാൽ പ്രമാണങ്ങളുടെ മായ്ക്കലും പുനഃസ്ഥാപിക്കലും താത്ക്കാലികമായി നിർത്തിവച്ചിരിക്കുന്നു.",
        "filedelete-maintenance-title": "പ്രമാണം മായ്ക്കാൻ കഴിയില്ല",
-       "mimesearch": "മൈം(MIME) തിരയൽ",
-       "mimesearch-summary": "ഈ താൾ പ്രമാണങ്ങളെ അവയുടെ മൈം(MIME)-തരം അനുസരിച്ച് അരിച്ചെടുക്കാൻ പ്രാപ്തമാക്കുന്നു:\nനൽകേണ്ടവിധം: പ്രമാണത്തിന്റെ തരം/ഉപതരം അല്ലെങ്കിൽ പ്രമാണത്തിന്റെ തരം/*, ഉദാ:<code>image/jpeg</code>.",
-       "mimetype": "മൈം(MIME) തരം:",
+       "mimesearch": "മൈം (MIME) തിരയൽ",
+       "mimesearch-summary": "ഈ താൾ പ്രമാണങ്ങളെ അവയുടെ മൈം (MIME)-തരം അനുസരിച്ച് അരിച്ചെടുക്കാൻ പ്രാപ്തമാക്കുന്നു:\nനൽകേണ്ടവിധം: പ്രമാണത്തിന്റെ തരം/ഉപതരം അല്ലെങ്കിൽ പ്രമാണത്തിന്റെ തരം/*, ഉദാ:<code>image/jpeg</code>.",
+       "mimetype": "മൈം (MIME) തരം:",
        "download": "ഡൗൺലോഡ്",
        "unwatchedpages": "ആരും ശ്രദ്ധിക്കാത്ത താളുകൾ",
        "listredirects": "തിരിച്ചുവിടൽ താളുകളുടെ പട്ടിക കാണിക്കുക",
        "apisandbox-dynamic-parameters": "കൂടുതലായുള്ള ചരങ്ങൾ",
        "apisandbox-dynamic-parameters-add-label": "ചരം ചേർക്കുക:",
        "apisandbox-dynamic-parameters-add-placeholder": "ചരത്തിന്റെ പേര്",
+       "apisandbox-add-multi": "കൂട്ടിച്ചേർക്കുക",
+       "apisandbox-submit-invalid-fields-title": "ചില മണ്ഡലങ്ങൾ അസാധുവാണ്",
        "apisandbox-results": "ഫലങ്ങൾ",
        "apisandbox-request-url-label": "അഭ്യർത്ഥനാ യൂ.ആർ.എൽ.:",
        "apisandbox-request-time": "അഭ്യർത്ഥനയുടെ സമയം: {{PLURAL:$1|$1 മി.സെ.}}",
        "confirmemail_body_set": "$1 എന്ന ഐ.പി. വിലാസത്തിൽ നിന്നും ആരോ ഒരാൾ, മിക്കവാറും താങ്കളായിരിക്കും,\n{{SITENAME}} സംരംഭത്തിലെ \"$2\" എന്ന അംഗത്വത്തിന്റെ ഇമെയിൽ വിലാസമായി ഈ വിലാസം നൽകിയിരിക്കുന്നു.\n\n{{SITENAME}} സംരംഭത്തിലെ ഈ അംഗത്വം താങ്കളുടെ തന്നെയാണെന്ന് ഉറപ്പാക്കാനും, \nഇമെയിൽ സൗകര്യങ്ങൾ സജ്ജമാക്കാനും ഈ കണ്ണി ബ്രൗസറിൽ തുറക്കുക:\n\n$3\n\nഈ അംഗത്വം താങ്കളുടേത് *അല്ല* എങ്കിൽ\nഇമെയിൽ വിലാസ സ്ഥിരീകരണം റദ്ദാക്കാൻ താഴെക്കൊടുത്തിരിക്കുന്ന കണ്ണി ഉപയോഗിക്കുക:\n\n$5\n\nഈ സ്ഥിരീകരണ കോഡ് $4-നു കാലഹരണപ്പെടുന്നതാണ്.",
        "confirmemail_invalidated": "ഇ-മെയിൽ വിലാസത്തിന്റെ സ്ഥിരീകരണം റദ്ദാക്കിയിരിക്കുന്നു",
        "invalidateemail": "ഇ-മെയിൽ വിലാസ സ്ഥിരീകരണം റദ്ദാക്കുക",
+       "notificationemail_subject_changed": "{{SITENAME}} സംരംഭത്തിൽ രജിസ്റ്റർ ചെയ്ത ഇമെയിൽ വിലാസം മാറിയിരിക്കുന്നു",
+       "notificationemail_subject_removed": "{{SITENAME}} സംരംഭത്തിൽ രജിസ്റ്റർ ചെയ്ത ഇമെയിൽ വിലാസം നീക്കംചെയ്തിരിക്കുന്നു",
+       "notificationemail_body_changed": "$1 ഐ.പി. വിലാസത്തിൽ നിന്ന് ആരോ ഒരാൾ, മിക്കവാറും താങ്കളായിരിക്കാം,\n{{SITENAME}} സംരംഭത്തിലെ \"$2\" എന്ന അംഗത്വത്തിന്റെ ഇമെയിൽ വിലാസം \"$3\" എന്നാക്കി മാറ്റിയിരിക്കുന്നു.\n\nഇത് താങ്കൾ അല്ലെങ്കിൽ, സൈറ്റിന്റെ അഡ്മിനിസ്ട്രേറ്ററെ അടിയന്തരമായി ബന്ധപ്പെടുക.",
+       "notificationemail_body_removed": "$1 ഐ.പി. വിലാസത്തിൽ നിന്ന് ആരോ ഒരാൾ, മിക്കവാറും താങ്കളായിരിക്കാം,\n{{SITENAME}} സംരംഭത്തിലെ \"$2\" എന്ന അംഗത്വത്തിന്റെ ഇമെയിൽ വിലാസം നീക്കം ചെയ്തിരിക്കുന്നു.\n\nഇത് താങ്കൾ അല്ലെങ്കിൽ, സൈറ്റിന്റെ അഡ്മിനിസ്ട്രേറ്ററെ അടിയന്തരമായി ബന്ധപ്പെടുക.",
        "scarytranscludedisabled": "[അന്തർവിക്കി ഉൾപ്പെടുത്തൽ സജ്ജമല്ല]",
        "scarytranscludefailed": "[$1-നു ഫലകം കണ്ടുപിടിക്കാൻ പറ്റിയില്ല]",
        "scarytranscludefailed-httpstatus": "[$1-നു ഫലകം എടുക്കാൻ കഴിഞ്ഞില്ല: എച്ച്.റ്റി.റ്റി.പി. $2]",
        "fileduplicatesearch-noresults": "\"$1\" എന്ന പേരിൽ ഒരു പ്രമാണവും കണ്ടെത്താനായില്ല.",
        "specialpages": "പ്രത്യേക താളുകൾ",
        "specialpages-note-top": "സൂചന",
+       "specialpages-note-restricted": "* പൊതുവേ ഉപയോഗിക്കുന്ന പ്രത്യേക താളുകൾ.\n* <span class=\"mw-specialpagerestricted\">ഉപയോഗം പരിമിതപ്പെടുത്തിയിരിക്കുന്ന പ്രത്യേക താളുകൾ.</span>",
        "specialpages-group-maintenance": "പരിചരണം ആവശ്യമായവ",
        "specialpages-group-other": "മറ്റു പ്രത്യേക താളുകൾ",
        "specialpages-group-login": "പ്രവേശിക്കുക / അംഗത്വമെടുക്കുക",
        "cannotauth-not-allowed-title": "അനുമതി നിഷേധിച്ചിരിക്കുന്നു",
        "cannotauth-not-allowed": "ഈ താൾ ഉപയോഗിക്കാൻ താങ്കൾക്ക് അനുവാദമില്ല",
        "credentialsform-account": "അംഗത്വ നാമം:",
+       "cannotlink-no-provider-title": "കണ്ണി ചേർക്കാവുന്ന അംഗത്വങ്ങൾ ഒന്നുമില്ല",
+       "cannotlink-no-provider": "കണ്ണി ചേർക്കാവുന്ന അംഗത്വങ്ങൾ ഒന്നുമില്ല",
+       "linkaccounts": "അംഗത്വങ്ങൾ കണ്ണി ചേർക്കുക",
+       "linkaccounts-success-text": "അംഗത്വം കണ്ണി ചേർത്തു.",
+       "linkaccounts-submit": "അംഗത്വങ്ങൾ കണ്ണി ചേർക്കുക",
+       "unlinkaccounts": "അംഗത്വങ്ങൾ കണ്ണി മാറ്റുക",
+       "unlinkaccounts-success": "അംഗത്വം കണ്ണി മാറ്റി.",
        "restrictionsfield-badip": "അസാധുവായ ഐ.പി. വിലാസം അല്ലെങ്കിൽ പരിധി:$1",
        "restrictionsfield-label": "അനുവദിച്ചിട്ടുള്ള ഐ.പി. പരിധികൾ:",
        "edit-error-short": "പിഴവ്: $1",
index 19efe88..e7aa2ae 100644 (file)
        "tog-usenewrc": "Molōloāzqueh in tlapatlaliztli in yancuīc tlapatlaliztli āmapan īhuān in tlachiyaliztli tlapōhualāmapan (monequi JavaScript)",
        "tog-showtoolbar": "Motlaīxtlatīz in tlachihchīhualōni pāntli",
        "tog-editondblclick": "Tiquimpatlāz in zāzanilli intlā ōme tiquimpachoa",
-       "tog-watchcreations": "Moaquiāz in āmatl mā niquinyōcoya īhuān in tlahcuilōlli mā niquinquetza īpan notlachiyaliz",
+       "tog-watchcreations": "Niquintlaliz in tlahcuilolamameh in oniquinchiuh ihuan in tlahcuilolpiyaliztin in oniquinquetz ipan notlachiyaliz",
        "tog-watchdefault": "Moaquiāz āmatl īhuān tlahcuilōlli mā niquinpatla in notlachiyaliz",
        "tog-watchmoves": "Moaquiāz āmatl īhuān tlahcuilōlli mā niquinzaca in notlachiyaliz",
-       "tog-watchdeletion": "Moaquiāz āmatl īhuān tlahcuilōlli mā niquimpolo in notlachiyaliz",
+       "tog-watchdeletion": "Niquintlaliz tlahcuilolamameh ihuan tlahcuilolpiyaliztin in oniquimpoloh ipan notlachiyaliz",
        "tog-minordefault": "Ticmachiyōtīz mochīntīn tlapatlalitzintli ic default",
        "tog-previewontop": "Tiquittāz achtochīhualiztli achtopa tlapatlaliztli caxitl",
        "tog-previewonfirst": "Xiquitta achtochīhualiztli inic cē tlapatlalizpan",
@@ -35,7 +35,7 @@
        "tog-enotifminoredits": "Notech moēhualtia cē maltzinteyōtl netitlaniztli nō ihcuāc mopatla tepitōn zāzanilli in notlachiyaliz.",
        "tog-enotifrevealaddr": "Ticnēxtīz mo e-mailcān āxcāncayōtechcopa āmatlacuilizpan",
        "tog-shownumberswatching": "Tiquinttāz tlatequitiltilīlli tlein tlachiyacateh",
-       "tog-oldsig": "Nicān tōcāyoh:",
+       "tog-oldsig": "Iuh otictlalih motoca:",
        "tog-fancysig": "Wikitext īpan ticmatiz tōcāyoh (in ahtleh auto-link)",
        "tog-forceeditsummary": "Xinēchnōtzāz ihcuāc ahmo niquihtōz inōn ōnitlapatlac",
        "tog-watchlisthideown": "Tictlatiz mopatlaliz ipan motlachiyaliz",
@@ -47,7 +47,7 @@
        "tog-diffonly": "Ahmo tiquittāz zāzanilli ītlapiyaliz ahneneuhquilitzīntlan",
        "tog-showhiddencats": "Mà monèxtìkàn in tlatlatìltìn tlaìxmatkàtlàlilòmë",
        "underline-always": "Mochipa",
-       "underline-never": "Aīc",
+       "underline-never": "Aic",
        "editfont-monospace": "Cencoyahualiztli machiyotlahtoliztli",
        "editfont-sansserif": "Sans-serif machiyotlahtoliztli",
        "editfont-serif": "Serif machiyotlahtoliztli",
@@ -78,7 +78,7 @@
        "november": "11 Metz",
        "december": "12 Metz",
        "january-gen": "Ic cē mētztli",
-       "february-gen": "Īcōmemētztli",
+       "february-gen": "Icomemetztli",
        "march-gen": "Īcyēyimētztli",
        "april-gen": "Ic nauhtetl metztli",
        "may-gen": "Īcmācuīllimētztli",
        "article": "Tlahcuilolamatl",
        "newwindow": "(Motlapoaz ce yancuic tlanexillotl)",
        "cancel": "Moxitiniz",
-       "moredotdotdot": "Huehca ōmpa...",
-       "mypage": "Noāmauh",
+       "moredotdotdot": "Ocachi...",
+       "mypage": "Notlahcuilolamauh",
        "mytalk": "Teixnamiquiliztli",
        "anontalk": "Teixnamiquiliztli",
        "navigation": "Panoliztli",
        "namespaces": "Tocatlacauhtli",
        "variants": "Nepapan",
        "navigation-heading": "Nemiliztlahtolpohualamatl",
-       "errorpagetitle": "Aiuhcāyōtl",
+       "errorpagetitle": "Ahiuhcayotl",
        "returnto": "Ximocuepa ihuic $1.",
        "tagline": "Itechcopa {{SITENAME}}",
        "help": "Tepalehuiliztli",
        "newpage": "Yancuic tlahcuilolli",
        "talkpagelinktext": "Teixnamiquiliztli",
        "specialpage": "Noncuahquizcatlahcuilolamatl",
-       "personaltools": "In tlein nitēquitiltilia",
+       "personaltools": "Motequitihuilocahuan",
        "talk": "Teixnamiquiliztli",
        "views": "Tlachiyaliztli",
        "toolbox": "Tequitihualoni",
        "imagepage": "Tiquittaz in tlahcuilolamatl itecpanaliztlapiyaliz",
        "mediawikipage": "Xiquitta tetitlaniliztli itlahcuilolamauh",
        "templatepage": "Xiquitta tlahcuilolamatl ineixcuitil",
-       "viewhelppage": "Xiquitta tēpalēhuiliztli zāzanilli",
+       "viewhelppage": "Xiquitta in tepalehuiliztli itlahcuilolamauh",
        "categorypage": "Tiquittaz neneuhcayotl itlahcuilolamauh",
-       "viewtalkpage": "Xiquitta tēixnāmiquiliztli zāzanilli",
-       "otherlanguages": "Occequintin tlahtlahtolcopa",
-       "redirectedfrom": "(Ōmotlacuep īhuīcpa $1)",
-       "redirectpagesub": "Ōmotlacuep zāzanilli",
-       "lastmodifiedat": "Inin tlahcuilolli omopatlac immanin $1, ipan $2.",
+       "viewtalkpage": "Xiquitta in teixnamiquiliztli",
+       "otherlanguages": "Occequi tlahtolli",
+       "redirectedfrom": "(Omocuep ihuicpa $1)",
+       "redirectpagesub": "Tlacueptli itlahcuilolamauh",
+       "lastmodifiedat": "Inin tlahcuilolamatl omopatlac immanin $1, ipan $2.",
        "viewcount": "Inīn zāzanilli quintlapōhua {{PLURAL:$1|cē tlahpololiztli|$1 tlahpololiztli}}.",
-       "protectedpage": "Ōmoquīxtix zāzanilli",
-       "jumpto": "Īhuīcpa ticholōz:",
+       "protectedpage": "Tlamaquixtilli tlahcuilolamatl",
+       "jumpto": "Ticholoz ihuicpa:",
        "jumptonavigation": "amapanoliztli",
        "jumptosearch": "Tlatemoliztli",
        "aboutsite": "Itechcopa {{SITENAME}}",
        "currentevents-url": "Project:Axcancayotl",
        "disclaimers": "tlamamalquixtiliztli",
        "edithelp": "Tepalehuiliztli ica tlapatlaliztli",
-       "helppage-top-gethelp": "Tēpalēhuiliztli",
+       "helppage-top-gethelp": "Tepalehuiliztli",
        "mainpage": "Yacatlahcuilolli",
        "mainpage-description": "Yacatlahcuilolli",
        "policy-url": "Project:Nahuatīltōn",
-       "portal": "Yacatlahcuilolli tocalpol",
-       "portal-url": "Project:Yacatlahcuilolli tocalpol",
+       "portal": "Necentlaliloyan",
+       "portal-url": "Project:Necentlaliloyan",
        "privacy": "Tlahcuilolli piyaliznahuatilli",
        "privacypage": "Project:Tlahcuilōlpiyaliztechcopa nahuatīltōn",
        "badaccess": "Tlahuelītiliztechcopa ahcuallōtl",
        "badaccess-group0": "Tehhuatl ahmo hueli ticchihua in tlein tiquelehuia.",
        "badaccess-groups": "Inin tlen tiquelehuia zan quichihuah tequitiuhqueh {{PLURAL:$2|itech necentlaliliztli| centetl itech inin $2 necentlaliliztin}}: $1.",
        "ok": "Cayecualli",
-       "retrievedfrom": "Ōquīzqui ītech  \"$1\"",
+       "retrievedfrom": "Oquiz itech  \"$1\"",
        "youhavenewmessages": "Tiquimpiya $1 ($2).",
        "youhavenewmessagesmulti": "Tiquimpiya yancuīc tlahcuilōlli īpan $1",
        "editsection": "Ticpatlaz",
        "toc": "In tlein quipiya inin tlahcuilolli",
        "showtoc": "xicnēxti",
        "hidetoc": "xictlāti",
-       "collapsible-collapse": "Motlàtìs",
-       "collapsible-expand": "Monèxtìs",
-       "confirmable-yes": "Quēmah",
-       "confirmable-no": "Ahmō",
-       "thisisdeleted": "¿Tiquittaz nozo ahticpolōz $1?",
-       "viewdeleted": "¿Tiquiēlēhuia tiquitta $1?",
-       "restorelink": "{{PLURAL:$1|cē tlapatlaliztli polotic|$1 tlapatlaliztli polotic}}",
-       "feedlinks": "Olōlpōl:",
+       "collapsible-collapse": "Motlatiz",
+       "collapsible-expand": "Monextiz",
+       "confirmable-yes": "Quemah",
+       "confirmable-no": "Ahmo",
+       "thisisdeleted": "¿Tiquittaz nozo ahticpoloz $1?",
+       "viewdeleted": "¿Cuix tiquittaznequi $1?",
+       "restorelink": "{{PLURAL:$1|cē tlapatlaliztli mopoloh|$1 tlapatlaliztin mopolohqueh}}",
+       "feedlinks": "Nemaccayotl:",
        "site-rss-feed": "$1 RSS huelītiliztli",
-       "site-atom-feed": "$1 Atom huelītiliztli",
+       "site-atom-feed": "Atom tlamantli itech $1",
        "page-rss-feed": "\"$1\" RSS huelītiliztli",
        "page-atom-feed": "\"$1\" RSS huelitiliztli",
        "red-link-title": "$1 (ahmo oncah tlahcuilolamatl)",
        "nosuchaction": "Ahmo ia tlachīhualiztli",
        "nosuchspecialpage": "Âmò ka inòn nònkuâkìskàtlaìxtlapalli",
        "nospecialpagetext": "<strong>Tiknẻki sè nònkuâkìskàtlaìxtlapalli tlèn âmò kä.</strong>\n\nKualli tikỉtas sè ìntlapòpòwaltekpànal in nònkuâkìskàtlaìxtlapaltìn ìpan [[Special:SpecialPages|{{int:specialpages}}]].",
-       "error": "Ahcuallōtl",
+       "error": "Ahiuhcayotl",
        "databaseerror": "Tlahcuilōltzintlān īahcuallo",
        "databaseerror-query": "Tlahtlanilli: $1",
+       "databaseerror-error": "Ahiuhcayotl $1",
        "laggedslavemode": "Xiquitta: huel ahmo ia achi yancuīc in tlapatlaliztli inīn zāzanilco.",
        "readonly": "Mactzīntlantli tzahtzacticah",
        "missingarticle-rev": "(tlachiyaliztli ītlapōhual: $1)",
        "missingarticle-diff": "(Ahneneuh.: $1, $2)",
-       "internalerror": "Ahcuallōtl tlahtic",
-       "internalerror_info": "Ahcuallōtl tlahtic: $1",
+       "internalerror": "Ahiuhcayotl tlahtic",
+       "internalerror_info": "Ahiuhcayotl tlahtic: $1",
        "filecopyerror": "Ahmō ōmohuelītic tlacopīna \"$1\" īhuīc \"$2\".",
        "filerenameerror": "Ahmō ōmohuelītic tlazaca \"$1\" īhuīc \"$2\".",
        "filedeleteerror": "Ahmō ōmohuelītic tlapoloa \"$1\".",
        "userlogout": "Xiquīza",
        "notloggedin": "Ahmō ōtimocalac",
        "userlogin-noaccount": "Cuix ahmo titlapohualeh?",
-       "createaccount": "Xicchīhua tlapōhualli",
+       "createaccount": "Xicchihua ce tlapohualli",
        "createacct-email-ph": "xiquihcuilo mocorreo electrónico",
        "createaccountmail": "Ticnemītīz ahmo cemihcac zāzoichtacātlahtōlli nō in tiquēhualtīz in maltzinteyōtl monetitlanizyeyān",
        "createacct-reason": "Tleīpampa",
        "retypenew": "Occeppa xiquihcuiloa yancuīc motlahtōlichtacayo:",
        "resetpass_submit": "Xicpatlāz motlahtōlichtacāyo auh xicalaquīz",
        "changepassword-success": "Moichtacātlahtōl ōmopatlac.",
+       "botpasswords-label-cancel": "Moxitiniz",
        "resetpass_forbidden": "Tlahtōlichtacayōtl ahmo mohuelītih mopatlah",
        "resetpass-submit-loggedin": "Ticpatlāz motlahtōlichtacāyo",
-       "resetpass-submit-cancel": "Xiccahua",
+       "resetpass-submit-cancel": "Moxitiniz",
        "passwordreset-username": "Tequihuihcātōcāitl:",
-       "bold_sample": "Tliltic tlahcuilolpiyaliz",
-       "bold_tip": "Tlīltic tlahcuilōlli",
+       "bold_sample": "Tliltic tlahcuiloliztli",
+       "bold_tip": "Tliltic tlahcuiloliztli",
        "italic_sample": "Nacacic tlahcuiloliztli",
        "italic_tip": "Nacacic tlahcuiloliztli",
        "link_sample": "Tzonhuiliztli ītōcā",
        "templatesused": "{{PLURAL:$1|Nemachiotl tlen motequiuhtia|Nemachiomeh tlen moquintequiuhtiah}} ipan inin tlahcuilolamatl:",
        "templatesusedpreview": "{{PLURAL:$1|Nemachiotl tlen motequiuhtia|Nemachiomeh tlen moquintequiuhtiah}} ipan inin achtochihualiztli:",
        "templatesusedsection": "{{PLURAL:$1|Nemachiotl tlen motequiuhtia|Nemachiomeh tlen moquintequiuhtiah}} ipan inin tlaxeloliztli:",
-       "template-protected": "(ōmoquīxti)",
+       "template-protected": "(ahmo moquixtia)",
        "hiddencategories": "Inin tlahcuilolli pohui {{PLURAL:$1|1 tlatlalilli neneuhcayotl|$1 tlatlaliltin neneuhcayomeh}}:",
        "nocreatetext": "Inin huiqui oquitzacuili ic mochihua yancuic tlahcuilolamatl. Quil ticcuepaznequi auh ticpatlaz occe tlahcuilolamatl, [[Special:UserLogin|xicalaqui nozo xicchihua ce cuentah]].",
        "nocreate-loggedin": "Ahmo hueli ticchihua yancuic tlahcuilolamatl.",
        "permissionserrors": "Tēmācāhualiztli aiuhcāyōtl",
        "permissionserrorstext": "Ahmo tihuelīti quichīhua inōn, inīn {{PLURAL:$1|īxtlamatilizpampa}}:",
        "permissionserrorstext-withaction": "Ahmo tiquihuelīti $2 inīn {{PLURAL:$1|īxtlamatilizpampa}}:",
-       "moveddeleted-notice": "Inin tlahcuilolamatl omopoloh.\nIn tlapololiztli ihuan in tlazacaliztli tlahcuilolloh cah tlani.",
+       "moveddeleted-notice": "Inin tlahcuilolamatl omopoloh.\nIn tlapololiztli ihuan in tlazacaliztli itlahcuilolloh cah tlani.",
        "edit-gone-missing": "Ahmo huelīti yancuīya zāzanilli.\nHueliz ōmopolo.",
        "edit-conflict": "Tlapatlaliztli yāōyōtōn",
        "edit-already-exists": "Ahmo mohuelīti mochīhua yancuīc zāzanilli.\nYe ia.",
        "content-model-javascript": "JavaScript",
        "cantcreateaccount-text": "[[User:$3|$3]] ōcquīxti cuentah tlachīhualiztli īpal inīn IP ('''$1''').\n\nĪxtlamatiliztli īpal $3 cah ''$2''",
-       "viewpagelogs": "Tiquinttāz tlahcuilōlloh inīn zāzaniltechcopa",
+       "viewpagelogs": "Tiquittaz in tlahcuilolamatl itlahtollouh",
        "nohistory": "Nicān ahmō oncah tlaīxtlapatlaliztlahtōllōtl.",
        "currentrev": "Āxcān tlapatlaliztli",
        "currentrev-asof": "Āxcān tlachiyaliztli īpan $1",
        "last": "xocoyoc",
        "page_first": "achto",
        "page_last": "xōcoyōc",
-       "history-fieldset-title": "Xitlatēmo īpan tlahtōllōtl",
+       "history-fieldset-title": "Xitlatemo ihtic tlahtollotl",
        "history-show-deleted": "Zan tlapololtin",
        "histfirst": "in achto",
        "histlast": "in tlatzaucticah",
        "historysize": "({{PLURAL:$1|1 byte|$1 byte}})",
-       "historyempty": "(iztāc)",
+       "historyempty": "(cactic)",
        "history-feed-title": "Tlaceppahuiliztlahtōllōtl",
        "history-feed-description": "Tlachiyaliztli tlahcuilōlloh inīn zāzaniltechcopa huiquipan",
        "history-feed-item-nocomment": "$1 īpan $2",
        "mergehistory-comment": "Ōmocēntili [[:$1]] īpan [[:$2]]: $3",
        "mergehistory-reason": "Tleīpampa:",
        "revertmerge": "Ticahtletiliz in cetiliztli",
-       "history-title": "«$1» ītlaceppahuiliztlahtōllo",
+       "history-title": "Nepapan tlahcuilollotl itechpa «$1»",
        "lineno": "Pantli $1:",
        "editundo": "Ticxitiniz",
        "searchresults": "motlatemoliz itlananquilizhuan",
        "searchprofile-articles": "Itech tlahcuilolamatl",
        "searchprofile-images": "Nepapan media",
        "searchprofile-everything": "Mochi",
-       "searchprofile-advanced": "Huehca ōmpa",
-       "searchprofile-articles-tooltip": "Tictēmōz īpan $1",
+       "searchprofile-advanced": "Quizqui",
+       "searchprofile-articles-tooltip": "Tictemoz ipan $1",
        "searchprofile-images-tooltip": "motemoz tlapiyaliztecpaliztli",
-       "searchprofile-everything-tooltip": "Tictēmōz mochi tlapiyalizpan (mopiyah tēixnāmiquiliztli zāzanilli)",
-       "search-result-size": "$1 ({{PLURAL:$2|1 tlahtōl|$2 tlahtōltin}})",
+       "searchprofile-everything-tooltip": "Tictemoz ipan mochi tlapiyaliztli (noihuan tlahcuilolamatl iteixnamiquiliz)",
+       "search-result-size": "$1 ({{PLURAL:$2|1 tlahtol|$2 tlahtoltin}})",
        "search-redirect": "(ixquichca ompa mitzhuica $1)",
        "search-section": "(tlahtōltzintli $1)",
        "search-category": "(neneuhcayotl $1)",
        "search-suggest": "Ahnōceh tiquihtōznequiya: $1",
        "search-interwiki-caption": "Tlachīhualiztli īcnīhuān",
-       "search-interwiki-more": "(huehca ōmpa)",
+       "search-interwiki-more": "(ocachi)",
        "search-relatedarticle": "Ītechcopa",
        "searchrelated": "ītechcopa",
        "searchall": "mochīntīn",
        "group": "Necentlaliliztli:",
        "group-user": "Tequihuihqueh",
        "group-bot": "Tepoztlācah",
-       "group-sysop": "Tlahcuilōlpixqueh",
+       "group-sysop": "Huiquipixqueh",
        "group-all": "(mochīntīn)",
        "group-user-member": "{{GENDER:$1|tlatequitiltilīlli}}",
        "group-bot-member": "{{GENDER:$1|tepozcuāyōllōtl}}",
-       "group-sysop-member": "{{GENDER:$1|tētlamahmacani}}",
+       "group-sysop-member": "{{GENDER:$1|huiquipixqui}}",
        "grouppage-user": "{{ns:project}}:Tlatequitiltilīlli",
        "grouppage-bot": "{{ns:project}}:Tepoztlācah",
-       "grouppage-sysop": "{{ns:project}}:Tlahcuilōlpixqueh",
-       "right-read": "Tiquimpōhuāz zāzaniltin",
-       "right-edit": "Tiquimpatlāz zāzaniltin",
-       "right-createpage": "Ticchīhuāz zāzaniltin (ahmo tēixnāmiquiliztli zāzaniltin)",
-       "right-createtalk": "Ticchīhuāz tēixnāmiquiliztli zāzaniltin",
+       "grouppage-sysop": "{{ns:project}}:Huiquipixqueh",
+       "right-read": "Tiquimpohuaz tlahcuilolamameh",
+       "right-edit": "Tiquimpatlaz tlahcuilolamameh",
+       "right-createpage": "Ticchihuaz tlahcuilolamameh (ahmo teixnamiquiliztli tlahcuilolamatl)",
+       "right-createtalk": "Ticchihuaz teixnamiquiliztli itlahcuilolamauh",
        "right-createaccount": "Ticchīhuaz yancuic tlapōhualli",
        "right-minoredit": "Ticpatlāz quemeh tlapatlalitzintli",
        "right-move": "Tiquinzacāz zāzaniltin",
        "recentchanges": "Yancuic tlapatlaliztli",
        "recentchanges-legend": "Yancuīc tlapatlaliztechcopa tlanequiliztli",
        "recentchanges-summary": "Tictoquiliz itlapatlaliz oc yancuic inahuac huiqui inin tlahcuilolpan.",
-       "recentchanges-label-newpage": "Inīn tlapatlaliztli ōquiyōcox cē yancuīc āmatl",
+       "recentchanges-label-newpage": "Inin tlapatlaliztli oquiyocox ce yancuic tlahcuilolamatl",
        "recentchanges-label-minor": " Inin tepiton tlapatlaliztli",
        "recentchanges-label-bot": "Inin tlapaltlaliztli oquichiuh ce robot",
+       "rcfilters-savedqueries-cancel-label": "Moxitiniz",
        "rclistfrom": "Xiquittaz yancuic tlapatlaliztli ixquichca $3 ihuicpa $2",
        "rcshowhideminor": "$1 tlapatlalitzintli",
        "rcshowhideminor-show": "Xicnexti",
        "sourceurl": "Mēyal-URL:",
        "destfilename": "Tōcāhuīc:",
        "watchthisupload": "Tictlachiyaz inin tecpanaliztlapiyaliztli",
+       "upload-dialog-button-cancel": "Moxitiniz",
        "upload-form-label-infoform-name": "Tōcāitl",
        "upload-form-label-usage-filename": "Ihcuilōlli ītōcā",
        "upload_source_file": "(ticpepenaz ce tlahcuilolli mochiuhpohualhuazco)",
        "listfiles-latestversion-yes": "Quēmah",
        "listfiles-latestversion-no": "Ahmō",
        "file-anchor-link": "Tlapiyaliztecpanaliztli",
-       "filehist": "Ihcuilōlli ītlahtōllo",
+       "filehist": "Tlahcuilolli itlahtollo",
        "filehist-deleteall": "tiquimpolōz mochīntīn",
        "filehist-deleteone": "xicpolo",
        "filehist-revert": "tlacuepāz",
        "filedelete-edit-reasonlist": "Xiquihto ipampa ticpohpoloznequi in",
        "mimesearch": "MIME tlatemoliztli",
        "mimetype": "MIME iuhcāyōtl:",
-       "download": "tictemōz",
+       "download": "tictemoz",
        "unwatchedpages": "Zāzaniltin ahmo motlachiya",
        "listredirects": "Tlacuepaliztli",
        "unusedtemplates": "Nemachiyōtīlli ahmotequitiltiah",
        "statistics-header-edits": "Tlapatlaliztli tlapōhualli",
        "statistics-header-users": "Tlatequitiltilīlli ītlapōhualiz",
        "statistics-articles": "Tlapiyaliztli zāzanilli",
-       "statistics-pages": "Zāzaniltin",
+       "statistics-pages": "Tlahcuilolamameh",
        "statistics-pages-desc": "Mochīntīn zāzaniltin huiquipan, mopiyah tēixnāmiquiliztli, tlacuepaliztli, etz.",
        "statistics-files": "Tlahcuilōlli ōmoquetz",
        "doubleredirects": "Ōntetl tlacuepaliztli",
        "deadendpages": "Ahtlaquīzaliztli zāzaniltin",
        "protectedpages": "Zāzaniltin ōmoquīxti",
        "protectedpages-indef": "Zan ahcāhuitl tlaquīxtiliztli",
-       "protectedpages-page": "Tlaīxtli",
+       "protectedpages-page": "Tlahcuilolamatl",
        "protectedpages-reason": "Tleīpampa",
        "protectedtitles": "Tōcāitl ōmoquīxtih",
        "listusers": "Tlatequitiltilīlli",
        "newpages": "Yancuic tlahcuiloltin",
        "newpages-username": "Tlatequitiltilīltōcāitl:",
        "ancientpages": "Huehcauh tlahcuilolamatl",
-       "move": "Ticzacāz",
+       "move": "Ticzacaz",
        "movethispage": "Ticzacāz inīn zāzanilli",
        "pager-newer-n": "{{PLURAL:$1|1 yancuic|$1 yancuicqueh}}",
        "pager-older-n": "{{PLURAL:$1|1 huehcauh|$1 huehcauhqueh}}",
        "booksources-search-legend": "Tiquixtemoz amoxtli itzintiliz",
        "booksources-search": "Tlatemoliztli",
        "specialloguserlabel": "Tlatequitiltilīlli:",
-       "speciallogtitlelabel": "Tōcāitl:",
+       "speciallogtitlelabel": "Ahciliztli (itoca nozo {{ns:user}}:tequitiuhqui itoca):",
        "log": "Tlahcuilolloh",
        "all-logs-page": "Mochintin nohuiyanyoh intlahcuilolhuan",
        "allpages": "Mochintin tlahcuilolamatl",
        "linksearch-line": "$1 tzonhuīlo īxquichca $2",
        "listusers-submit": "Tiquittāz",
        "activeusers-submit": "Xiquitta",
-       "listgrouprights-group": "Olōlli",
+       "listgrouprights-group": "Necentlaliliztli",
        "listgrouprights-rights": "Huelītiliztli",
        "emailuser": "Tiquēhualtlīz maltzinteyōtl netitlaniztli inīn tlatequitiltilīlli",
        "defemailsubject": "{{SITENAME}} correo tlatequitiltilīlhuīc $1",
        "mywatchlist": "Notlachiyaliz",
        "watchnologin": "Ahmo ōtimocalac",
        "removedwatchtext": "Zāzanilli \"[[:$1]]\" ōmopolo [[Special:Watchlist|motlachiyalizco]].",
-       "watch": "Tictlachiyāz",
+       "watch": "Titlachiyaz",
        "watchthispage": "Tictlachiyāz inīn zāzanilli",
        "unwatch": "Ahmo titlachiyaz",
-       "watchlist-details": "{{PLURAL:$1|$1 zāzanilli|$1 zāzaniltin}} motlachiyaliz, ahmo mopōhua tēixnāmiquiliztli.",
-       "wlshowlast": "Tiquinttāz tlapatlaliztli īhuīcpa achto $1 yēmpohualminuhtli, $2 tōnaltin",
+       "watchlist-details": "Oncah {{PLURAL:$1|$1 tlahcuilolamatl|$1 tlahcuilolamameh}} ipan motlachiyaliz (oc tlahcuilolamatl iteixnamiquiliz).",
+       "wlshowlast": "Tiquittaz itlapatlaliz itech $1 horas, $2 tonaltin",
        "watching": "Tlachiyacah...",
        "unwatching": "Ahtlachiyacah...",
        "enotif_impersonal_salutation": "tlatequitiltilīlli īpan {{SITENAME}}",
        "protect-expiry-options": "1 hora:1 hour,1 tonalli:1 day,1 chicueyilhuitl:1 week,2 chicueyilhuitl:2 weeks,1 metztli:1 month,3 metztli:3 months,6 metztli:6 months,1 xihuitl:1 year,mochipa:infinite",
        "restriction-type": "Temacahualiztli:",
        "restriction-edit": "xicpatla",
-       "restriction-move": "Ticzacāz",
+       "restriction-move": "Ticzacaz",
        "restriction-create": "Ticchīhuāz",
        "restriction-upload": "Tlahcuilōlquetza",
        "undelete": "Tiquimittaz tlahcuilolamameh tlen omopohpolohqueh",
        "contributions": "In {{GENDER:$1|tlatequitiltilīlli}} ītlahcuilōl",
        "contributions-title": "Tlatequitiltilīlli $1 ītlahcuilōl",
        "mycontris": "Notlahcuilol",
-       "contribsub2": "$1 ($2)",
+       "contribsub2": "Ihuicpa {{GENDER:$3|$1}} ($2)",
        "uctop": "(axcan tlapatlaliztli)",
-       "month": "Īhuīcpa mētztli (auh achtopa):",
-       "year": "Xiuhhuīcpa (auh achtopa):",
+       "month": "Metzpan (auh yeppa):",
+       "year": "Xiuhpan (auh yeppa):",
        "sp-contributions-newbies": "Tiquinttāz zan yancuīc tlatequitiltilīlli īntlapatlaliz",
        "sp-contributions-newbies-sub": "Ic yancuīc",
        "sp-contributions-newbies-title": "Yancuīc tlatequitiltilīlli ītlahcuilōl",
        "whatlinkshere-page": "Tlahcuilolamatl:",
        "linkshere": "Inīn zāzaniltin quitzonhuiliah '''[[:$1]]''' īhuīc:",
        "nolinkshere": "Ahtle quitzonhuilia '''[[:$1]]''' īhuīc.",
-       "isredirect": "ōmotlacuep zāzanilli",
+       "isredirect": "Tlacueptli tlahcuilolamatl",
        "isimage": "īxiptlahtli tzonhuiliztli",
        "whatlinkshere-prev": "{{PLURAL:$1|achtopa|$1 achtopa}}",
        "whatlinkshere-next": "{{PLURAL:$1|niman|$1 niman}}",
        "whatlinkshere-links": "← tzohuiliztin",
        "whatlinkshere-hideredirs": "$1 tlacuepaliztli",
        "whatlinkshere-hidelinks": "$1 tzonhuiliztli",
-       "whatlinkshere-hideimages": "$1 tlahcuilōltzonhuīliztli",
+       "whatlinkshere-hideimages": "$1 tlahcuiloltzonhuiliztli",
        "whatlinkshere-filters": "Tlatzetzelōni",
        "blockip": "Tiquitzacuilīz tlatequitiltilīlli",
        "ipaddressorusername": "IP nozo tlatequitiltilīlli ītōcā:",
        "change-blocklink": "Ticpatlaz tlatzacualli",
        "contribslink": "tlapatlaliztli",
        "blocklogpage": "Tlatequitiltilīlli ōmotzacuili",
-       "move-page": "Ticzacāz $1",
+       "move-page": "Ticzacaz $1",
        "move-page-legend": "Tictocapatlaliz inin tlahcuilolamatl",
        "movepagetext": "Nicān mohcuiloa quemeh ticzacāz cē zāzanilli auh mochi in ītlahcuillōloh īhuīc occē yancuīc ītōca.\nHuēhuehtōcāitl yez tlacuepaliztli yancuīc tōcāhuīc.\nTzonhuiliztli huēhuehzāzanilhuīc ahmo mopatlāz.\nXiquitta ic māca xicchīhua [[Special:DoubleRedirects|ōntlacuepaliztli]] ahnozo [[Special:BrokenRedirects|tzomoc]].\nTitzonhuilizpiyāz.\n\nXicmati in zāzanilli ahmo mozacāz intlā ye ia cē zāzanilli tōcātica, zan cah iztāc zāzanilli ahnozo tlacuepaliztli īca ahmo tlahcuilōlloh.\nQuihtōznequi tihuelītīz ticuepāz cē zāzanilli īhuīc ītlācatōca intlā ahcuallōtl ticchīhuāz, tēl ahmo tihuelītīz occeppa tihcuilōz īpan zāzanilli tlein ia.\n\n'''¡XICPŌHUA!'''\nHueliz cah inīn huēyi tlapatlaliztli. Timitztlātlauhtia ticmatīz cuallōtl auh ahcuallōtl achtopa ticzacāz.",
        "movenotallowed": "Ahmo tihuelīti tiquinzaca zāzaniltin.",
        "movelogpage": "Tlazacaliztli tlahcuilōlloh",
        "movereason": "Īxtlamatiliztli:",
        "revertmove": "tlacuepāz",
-       "delete_and_move_confirm": "Quēmah, ticpolōz in zāzanilli",
-       "immobile-source-namespace": "Ahmo huelīti mozaca zāzanilli tōcātzimpan \"$1\"",
-       "immobile-target-namespace": "Ahmo huelīti mozaca zāzanilli tōcātzinhuīc \"$1\"",
-       "immobile-source-page": "Ahmo huelīti mozacāz zāzanilli.",
+       "delete_and_move_confirm": "Quemah, ticpohpoloz in tlahcuilolamatl",
+       "immobile-source-namespace": "Ahmo hueli ticzaca inin tlahcuilolamatl ompa \"$1\"",
+       "immobile-target-namespace": "Ahmo hueli ticzaca inin tlahcuilolamatl ompa\"$1\"",
+       "immobile-source-page": "Ahmo hueli occepa tictocayotia inin tlahcuilolamatl.",
        "move-leave-redirect": "Ma ticcahua ce tlatzonhuiliztli",
        "export": "Tiquinnamacāz zāzaniltin",
        "export-submit": "Ticnamacāz",
        "allmessages-filter-all": "Mochi",
        "allmessages-language": "Tlahtolli:",
        "allmessages-filter-submit": "Tiyaz",
-       "thumbnail-more": "Tiquihuēyiyāz",
+       "thumbnail-more": "Tichueyiyaz",
        "thumbnail_error": "Aiuhcāyōtl ihcuāc mochīhuaya tepitōntli: $1",
        "import": "Tiquincōhuāz zāzaniltin",
        "import-interwiki-sourcewiki": "Mēyalhuiqui:",
        "tooltip-ca-edit": "Ticpatlaz inin tlahcuilolli",
        "tooltip-ca-addsection": "Ticpehualiz ce yancuic xeliuhcayotl.",
        "tooltip-ca-viewsource": "Inīn zāzanilli ōmoquīxti. Tihuelīti tiquitta ītlahtōlcaquiliztilōni.",
-       "tooltip-ca-history": "Achtopa āxcān zāzanilli īhuān in tlatequitiltilīlli ōquinchīuhqueh",
+       "tooltip-ca-history": "In tlein ye oquichiuhqueh ipan inin tlahcuilolamatl",
        "tooltip-ca-protect": "Ticquīxtiāz inīn zāzanilli",
        "tooltip-ca-delete": "Ticpolōz inīn zāzanilli",
        "tooltip-ca-undelete": "Ahticpolōz inīn zāzanilli",
        "tooltip-ca-move": "Ticzacaz inin tlahcuilolamatl",
-       "tooltip-ca-watch": "Ticcentiliz inin tlahtolli motecpanaliz",
+       "tooltip-ca-watch": "Tictlaliz inin tlahcuilolamatl motlachiyaliz",
        "tooltip-ca-unwatch": "Ticpohpoloz inin tlahcuilolamatl ipan motlachiyaliz",
        "tooltip-search": "Tlatemoliztli ipan {{SITENAME}}",
        "tooltip-search-go": "Tiyaz ihuicpa tlahcuilolamatl ica inin huel melahuac tocaitl intla oncah",
        "tooltip-p-logo": "Tiquittaz in yacatlahcuilolli",
        "tooltip-n-mainpage": "Tiquittaz in yacatlahcuilolli",
        "tooltip-n-mainpage-description": "Tiquittaz in yacatlahcuilolli",
-       "tooltip-n-portal": "Tlachīhualiztechcopa, inōn tihuelīti titlachīhua, tlatēmoyān",
-       "tooltip-n-recentchanges": "Yancuic īpan tlapatlaliztli in huiqui",
+       "tooltip-n-portal": "Itech totequitiliz, in canin titlachihua, in canin titlatemoa",
+       "tooltip-n-recentchanges": "Iyancuictlapatlalizhuan ipan huiqui",
        "tooltip-n-randompage": "Tiquittaz cecen tlahcuilolli",
-       "tooltip-n-help": "In tēmachtīlōyān",
+       "tooltip-n-help": "In canin ticmachtiz",
        "tooltip-t-whatlinkshere": "Mochintin tlahcuiloltin huiquipan quitzonhuiliah nican",
        "tooltip-t-recentchangeslinked": "Yancuic tlapatlaliztli ipan tlahcuiloltin tlein quitzonhuilia nican",
        "tooltip-feed-rss": "RSS tlachicahualiztli inin tlahcuilolamatl",
        "tooltip-feed-atom": "Atom tlachicāhualiztli inīn zāzaniltechcopa",
        "tooltip-t-contributions": "Tlapōhualmatl ītechpa {{GENDER:$1|inīn tlatequitiltilīlli}} ītlahcuilōl",
-       "tooltip-t-emailuser": "Tiquihcuilōz inīn tlatequitiltililhuīc",
+       "tooltip-t-emailuser": "Tictitlantiz ce mail ihuicpa {{GENDER:$1|inin tequitiuhqui}}",
        "tooltip-t-upload": "Tiquinquetzaz tlahcuiloltin",
        "tooltip-t-specialpages": "Intlahtoltecpanaliz mochtin in noncuahquizquitlahcuiloltin",
        "tooltip-t-print": "Tepoztlahcuilolli",
        "tooltip-ca-nstab-main": "Tiquittaz tlein quipiya in tlahcuilolli",
        "tooltip-ca-nstab-user": "Xiquitta tequitiuhqui itlahcuilolamauh",
-       "tooltip-ca-nstab-special": "Inīn nōncuahquīzqui āmatl, auh ahmohuelitizpatla",
+       "tooltip-ca-nstab-special": "Inin noncuahquizqui amatl, auh ahmohueli in ticpatlaz",
        "tooltip-ca-nstab-project": "Xiquitta in tlayecantequitl itlahcuilolamauh",
-       "tooltip-ca-nstab-image": "Xiquittāz īxipzāzanilli",
+       "tooltip-ca-nstab-image": "Xiquittaz tlahcuilolpiyalli",
        "tooltip-ca-nstab-mediawiki": "Xiquitta in tlahcuilōltzin",
        "tooltip-ca-nstab-template": "Xiquitta in nemachiyōtīlli",
        "tooltip-ca-nstab-help": "Xiquitta in tēpalēhuiliztli zāzanilli",
        "tooltip-summary": "Xiquihcuilo ce tepiton tlahcuiloltontli",
        "anonymous": "Ahtōcāitl {{PLURAL:$1|tlatequitiltilīlli}} īpan {{SITENAME}}",
        "siteuser": "$1 tlatequitiltilīlli īpan {{SITENAME}}",
-       "lastmodifiedatby": "Inin tlahcuilolamatl omopatlac ipan $2, $1 ipal $3.",
+       "lastmodifiedatby": "$3 oquipatlac inin tlahcuilolamatl ipan tonalli $1 immanin $2.",
        "others": "occequīntīn",
        "siteusers": "$1 {{PLURAL:$2|{{GENDER:$1|tequitiuhqui}}|tequitiuhqueh}} īpan {{SITENAME}}",
        "spam_reverting": "Mocuepacah īhuīc xōcoyōc tlapatlaliztli ahmo tzonhuilizca īhuīc $1",
        "exif-lightsource-1": "Tōnameyōtl",
        "exif-lightsource-2": "Nāltic",
        "exif-lightsource-4": "Flax",
-       "exif-lightsource-10": "Mixxoh",
-       "exif-lightsource-11": "Ecahuīlli",
-       "exif-lightsource-12": "Nāltic tōnallāhuīlli (D 5700 – 7100K)",
-       "exif-lightsource-13": "Nāltic iztāc tōnallāhuīlli (N 4600 – 5400K)",
-       "exif-lightsource-14": "Nāltic cecec iztāc (W 3900 – 4500K)",
+       "exif-lightsource-10": "Mixtentoc",
+       "exif-lightsource-11": "Ecahuilloh",
+       "exif-lightsource-12": "Naltonac (D 5700 – 7100K)",
+       "exif-lightsource-13": "Iztac naltonac (N 4600 – 5400K)",
+       "exif-lightsource-14": "Cecec iztac naltonac (W 3900 – 4500K)",
        "exif-lightsource-15": "Nāltic iztāc (WW 3200 – 3700K)",
        "exif-lightsource-17": "Yēctli tlāhuīlli A",
        "exif-lightsource-18": "Yēctli tlāhuīlli B",
        "scarytranscludetoolong": "[In URL huel hueyac]",
        "recreate": "Ticchīhuāz occeppa",
        "confirm_purge_button": "Cualli",
-       "imgmultipageprev": "← achto zāzanilli",
-       "imgmultipagenext": "niman zāzanilli →",
+       "imgmultipageprev": "← achto tlahcuilolamatl",
+       "imgmultipagenext": "niman tlahcuilolamatl →",
        "imgmultigo": "¡Ma xiyauh!",
        "imgmultigoto": "Yaliztica ihuicpa tlahtolamatl $1",
        "ascending_abbrev": "quetza",
        "descending_abbrev": "temoa",
-       "table_pager_next": "Niman zāzanilli",
-       "table_pager_prev": "Achto zāzanilli",
-       "table_pager_first": "Achtopa zāzanilli",
-       "table_pager_last": "Xōcoyōc zāzanilli",
+       "table_pager_next": "Niman tlahcuilolamatl",
+       "table_pager_prev": "Achto tlahcuilolamatl",
+       "table_pager_first": "Achtopa tlahcuilolamatl",
+       "table_pager_last": "Xocoyoc tlahcuilolamatl",
        "table_pager_limit_submit": "Yaliztica",
        "table_pager_empty": "Ahtlein",
        "autosumm-blank": "Tlaiztaliztli in tlahcuilolamatl",
        "size-megabytes": "$1 MB",
        "size-gigabytes": "$1 GB",
        "watchlistedit-normal-title": "Ticpatlāz motlachiyaliz",
+       "watchlistedit-raw-titles": "Tlahcuilolamameh",
        "watchlistedit-raw-added": "{{PLURAL:$1|Ōmocentili cē zāzanilli|Ōmocentilih $1 zāzaniltin}}:",
+       "watchlistedit-clear-titles": "Tocaitl",
        "watchlisttools-view": "Tiquinttāz huēyi tlapatlaliztli",
        "watchlisttools-edit": "Tiquittāz auh ticpatlāz motlachiyaliz",
        "version": "Machiyōtzin",
        "specialpages-group-highuse": "Zāzaniltin tlatequitiliztechcopa",
        "specialpages-group-pages": "Mochintin tlahcuilolamameh",
        "specialpages-group-redirects": "Tlatēmoliztli īhuān  tlacuepaliztli",
-       "blankpage": "Iztāc zāzanilli",
-       "htmlform-selectorother-other": "Occe",
+       "blankpage": "Iztac tlahcuilolamatl",
+       "htmlform-selectorother-other": "Occequi",
        "rightsnone": "ahtlein",
+       "feedback-cancel": "Moxitiniz",
        "searchsuggest-search": "Tlatemoliztli",
        "api-error-stashfailed": "Tlâtek îtlakawilistli: In tlatèmakani awel òkeuh in èwalpanòni.",
        "api-error-unknown-warning": "Âmò ìxmatkàyo tlanawatilistli: \"$1\".",
        "special-characters-group-thai": "Taitlahcuilōlli",
        "special-characters-group-lao": "Laotlahcuilōlli",
        "special-characters-group-khmer": "Jemertlahcuilōlli",
-       "randomrootpage": "Zazantlen nelhuatlahcuilolamatl"
+       "randomrootpage": "Itech nepapan tlahcuilolamatl, iteyacatica"
 }
index 21dfa69..5f6a0a5 100644 (file)
@@ -88,7 +88,7 @@
        "tog-watchlisthideminor": "Skjul mindre endringer fra overvåkningslisten",
        "tog-watchlisthideliu": "Skjul endringer av innloggede brukere fra overvåkningslisten",
        "tog-watchlistreloadautomatically": "Oppdater oversiktslisten automatisk når et filter er endret (JavaScript kreves)",
-       "tog-watchlistunwatchlinks": "Legg til lenker for å overvåke/fjerne overvåking direkte i overvåkningslisten (JavaScript kreves)",
+       "tog-watchlistunwatchlinks": "Legg til lenker for å innføre/fjerne overvåking direkte i overvåkningslisten (JavaScript kreves)",
        "tog-watchlisthideanons": "Skjul endringer av anonyme brukere fra overvåkningslisten",
        "tog-watchlisthidepatrolled": "Skjul patruljerte endringer fra overvåkningslisten",
        "tog-watchlisthidecategorization": "Skjul kategorisering av sider",
        "prefs-dateformat": "Datoformat",
        "prefs-timeoffset": "Tidsforskyvning",
        "prefs-advancedediting": "Generelle valg",
+       "prefs-developertools": "Utviklerverktøy",
        "prefs-editor": "Tekstbehandling",
        "prefs-preview": "Forhåndsvisning",
        "prefs-advancedrc": "Avanserte alternativ",
        "rcfilters-filter-humans-label": "Menneske (ikke bot)",
        "rcfilters-filter-humans-description": "Redigeringer gjort av menneskelige brukere.",
        "rcfilters-filtergroup-reviewstatus": "Gjennomgangsstatus",
+       "rcfilters-filter-reviewstatus-unpatrolled-description": "Redigeringer som ikke er manuelt eller automatisk merket som patruljerte.",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "Upatruljert",
+       "rcfilters-filter-reviewstatus-manual-description": "Redigeringer som er manuelt merket som patruljert.",
+       "rcfilters-filter-reviewstatus-manual-label": "Patruljert manuelt",
+       "rcfilters-filter-reviewstatus-auto-description": "Redigeringer utført av avanserte brukere på innhold som er automatisk merket som patruljert.",
+       "rcfilters-filter-reviewstatus-auto-label": "Autopatruljert",
        "rcfilters-filtergroup-significance": "Betydning",
        "rcfilters-filter-minor-label": "Mindre endringer",
        "rcfilters-filter-minor-description": "Redigeringer merket som mindre av brukeren.",
        "recentchangeslinked-feed": "Relaterte endringer",
        "recentchangeslinked-toolbox": "Relaterte endringer",
        "recentchangeslinked-title": "Endringer relatert til «$1»",
-       "recentchangeslinked-summary": "Skriv inn et sidenavn for å se endringer på sider som lenker til eller lenkes fra den siden. (For å se medlemmene av en kategori, skriv inn Kategori:Kategorinavn.) Endringer på sider som er på [[Special:Watchlist|overvåkningslista di]] er i <strong>fet skrift</strong>.",
+       "recentchangeslinked-summary": "Skriv inn et sidenavn for å se endringer på sider som lenker til eller lenkes fra den siden. (For å se medlemmene av en kategori, skriv inn Kategori:Kategorinavn.) Endringer på sider som er på din [[Special:Watchlist|overvåkningsliste]] er angitt med <strong>fet skrift</strong>.",
        "recentchangeslinked-page": "Sidenavn:",
        "recentchangeslinked-to": "Vis endringer på sider som lenker til den gitte siden istedet",
        "recentchanges-page-added-to-category": "[[:$1]] ble lagt til i kategorien",
        "deadendpages": "Blindveisider",
        "deadendpagestext": "Følgende sider lenker ikke til andre sider på {{SITENAME}}.",
        "protectedpages": "Beskyttede sider",
+       "protectedpages-filters": "Filtre:",
        "protectedpages-indef": "Kun beskyttelser på ubestemt tid",
        "protectedpages-summary": "Denne siden viser en liste av eksisterende sider som for tiden er beskyttet. For å se en liste av sider som er beskyttet mot opprettelse, se [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]].",
        "protectedpages-cascade": "Kun dypbeskyttelse",
        "version-specialpages": "Spesialsider",
        "version-parserhooks": "Parsertillegg",
        "version-variables": "Variabler",
+       "version-editors": "Bidragsytere",
        "version-antispam": "Søppelpostforebygging",
        "version-other": "Annet",
        "version-mediahandlers": "Mediehåndterere",
        "unlinkaccounts-success": "Kontoens lenking ble fjernet.",
        "authenticationdatachange-ignored": "Autentiseringsdataendringen ble ikke håndtert. Muligens ble ingen tilbyder konfigurert?",
        "userjsispublic": "Merk: JavaScript-undersidene bør ikke inneholde konfidensielle data, siden de kan ses av andre brukere.",
+       "userjsonispublic": "OBS: JSON-undersider bør ikke inneholde privat informasjon ettersom de kan leses av andre brukere.",
        "usercssispublic": "Merk: CSS-undersidene bør ikke inneholde konfidensielle data siden de kan ses av andre brukere.",
        "restrictionsfield-badip": "Ugyldig IP-adresse eller intervall: $1",
        "restrictionsfield-label": "Tillatte IP-intervaller:",
index b8dd678..c023aac 100644 (file)
        "filereadonlyerror": "Kon t bestaand \"$1\" niet anpassen umdat de bestaandsmap \"$2\" op dit moment op allinnig-lezen steet.\n\nDe op-egeven reden is: \"$3\".",
        "invalidtitle-knownnamespace": "Ongeldige titel mit naamruumte \"$2\" en tekste \"$3\"",
        "invalidtitle-unknownnamespace": "Ongeldige titel mit onbekend naamruumtenummer $1 en tekste \"$2\"",
-       "exception-nologin": "Nyt an-emeld",
+       "exception-nologin": "Neet an-emelded",
        "exception-nologin-text": "Um disse zied te bekieken of disse haandeling uut te kunnen voeren mu'j [[Special:Userlogin|an-emeld]] ween bie disse wiki.",
        "virus-badscanner": "Slichte konfigurasie: onbekend antivirusprogramma: ''$1''",
        "virus-scanfailed": "inlezen is mislokt (kode $1)",
        "nav-login-createaccount": "Anmelden",
        "logout": "Ofmelden",
        "userlogout": "Aofmelden",
-       "notloggedin": "Nyt an-emeld",
+       "notloggedin": "Neet an-emelded",
        "userlogin-noaccount": "Heb jy noch gyn gebrukersname?",
        "userlogin-joinproject": "Wörd lid van {{SITENAME}}",
        "createaccount": "Inskryven",
        "enhancedrc-history": "geschiedenisse",
        "recentchanges": "Lätste wysigingen",
        "recentchanges-legend": "Opsies veur leste wiezigingen",
-       "recentchanges-summary": "Op disse zied ku'j de leste wiezigingen van disse wiki bekieken.",
+       "recentchanges-summary": "Up disse syde kün jy de lätste wysigingen van disse wiki bekyken.",
        "recentchanges-noresult": "Der waren in disse periode gien wiezigingen die an de kriteria voldoon.",
        "recentchanges-feed-description": "Zeuk naor de alderleste wiezingen op disse wiki in disse voer.",
-       "recentchanges-label-newpage": "Mid disse bewarking is een nye syde an-emaakt",
-       "recentchanges-label-minor": "Dit is een kleyne wysiging",
+       "recentchanges-label-newpage": "Mid disse bewarking is een nye syde an-emaked",
+       "recentchanges-label-minor": "Dit is een kleine wysiging",
        "recentchanges-label-bot": "Disse bewarking is uutevoord döär een bot",
-       "recentchanges-label-unpatrolled": "Disse bewarking is noch nyt nå-ekeaken",
+       "recentchanges-label-unpatrolled": "Disse bewarking is noch neet nå-ekeaken",
        "recentchanges-label-plusminus": "Disse sydegroutte is mid dit antal bytes ewysigd",
        "recentchanges-legend-heading": "<strong>Legenda:</strong>",
-       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (sy ouk de [[Special:NewPages|lyste mid nye syden]])",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (see ouk de [[Special:NewPages|lyste mid nye syden]])",
        "recentchanges-submit": "Bekiek",
        "rcfilters-legend-heading": "<strong>Lyste mid ofkortingen:</strong>",
        "rcfilters-group-results-by-page": "Resultaoten per zied groeperen",
        "rcfilters-savedqueries-add-new-title": "Filterinstellingen upslån",
        "rcfilters-restore-default-filters": "Standardfilters weerummezetten",
        "rcfilters-clear-all-filters": "Alle filters vortdoon",
-       "rcfilters-search-placeholder": "Filter wysigingen (gebruuk et menu of söök up filtername)",
+       "rcfilters-search-placeholder": "Filter wysigingen (gebruuk et menü of söök up filtername)",
        "rcfilters-filterlist-feedbacklink": "Låt uns weaten wat jy van disse (nye) filterhülpmiddels vinden",
        "rcfilters-highlightbutton-title": "Resultåten markeren",
        "rcfilters-highlightmenu-title": "Kies n kleur",
        "rcfilters-filtergroup-userExpLevel": "Gebrukersanmelding en ervåring",
        "rcfilters-filter-user-experience-level-registered-label": "An-emeld",
        "rcfilters-filter-user-experience-level-registered-description": "An-emelde bewarkers.",
-       "rcfilters-filter-user-experience-level-unregistered-label": "Nyt an-emeld",
-       "rcfilters-filter-user-experience-level-unregistered-description": "Bewarkers dy nyt an-emeld binnen.",
+       "rcfilters-filter-user-experience-level-unregistered-label": "Neet an-emelded",
+       "rcfilters-filter-user-experience-level-unregistered-description": "Bewarkers dee neet an-emelded binnen.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Anwas",
-       "rcfilters-filter-user-experience-level-newcomer-description": "An-emelde bewarkers dy minder as 10 bewarkingen edån hebben of 4 dagen aktiv ewest binnen.",
+       "rcfilters-filter-user-experience-level-newcomer-description": "An-emelden bewarkers dee minder as 10 bewarkingen edån hebben of 4 dagen aktiv ewesd hebben.",
        "rcfilters-filter-user-experience-level-learner-label": "Learlingen",
-       "rcfilters-filter-user-experience-level-learner-description": "An-emelde bewarkers mid meer ervåring as \"anwas\", mär minder as \"ervåren gebrukers\".",
+       "rcfilters-filter-user-experience-level-learner-description": "An-emelde bewarkers mid meyr ervåring as \"anwas\", mär minder as \"ervåren gebrukers\".",
        "rcfilters-filter-user-experience-level-experienced-label": "Ervåren gebrukers",
-       "rcfilters-filter-user-experience-level-experienced-description": "An-emelde bewarkers mid meer as 500 bewarkingen en 30 dagen van aktiviteyt.",
+       "rcfilters-filter-user-experience-level-experienced-description": "An-emelde bewarkers mid meyr as 500 bewarkingen en 30 dagen van aktiviteit.",
        "rcfilters-filter-bots-label": "Bot",
-       "rcfilters-filter-humans-label": "Meanskelik (gyn bot)",
+       "rcfilters-filter-humans-label": "Meanskelik (geyn bot)",
        "rcfilters-filter-humans-description": "Bewarkingen döär meanskelike bewarkers.",
        "rcfilters-filtergroup-reviewstatus": "Beoordelingsstaotus",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "Niet nao-ekeken",
        "rcfilters-filtergroup-significance": "Petansie",
-       "rcfilters-filter-minor-label": "Kleyne bewarkingen",
-       "rcfilters-filter-major-label": "Gyn kleyne bewarking",
+       "rcfilters-filter-minor-label": "Kleine bewarkingen",
+       "rcfilters-filter-minor-description": "Bewarkingen dee döär de bewarker emarkeerd binnen as klein.",
+       "rcfilters-filter-major-label": "Geyn kleine bewarking",
        "rcfilters-filter-major-description": "Bewarkingen niet emarkeerd as klein.",
        "rcfilters-filtergroup-watchlist": "Evolgde ziejen",
        "rcfilters-filter-watchlist-watched-label": "Op de volglieste",
        "rcfilters-filter-watchlist-watchednew-label": "Nieje volgliestwiezigingen",
        "rcfilters-filter-watchlist-notwatched-label": "Niet op de volglieste",
        "rcfilters-filter-watchlist-notwatched-description": "Alles behalve wiezigingen an ziejen die op joew volglieste staon.",
+       "rcfilters-filtergroup-changetype": "Soort wysiging",
        "rcfilters-filter-pageedits-label": "Sydebewarkingen",
        "rcfilters-filter-pageedits-description": "Wysigingen an de wiki-inhold, diskussys, kategorybeskryvingen…",
        "rcfilters-filter-newpages-label": "Nye syden",
        "rcfilters-filter-newpages-description": "Bewarkingen wårmead jy een nye syde anmaken.",
        "rcfilters-filter-categorization-label": "Kategorywysigingen",
-       "rcfilters-filter-categorization-description": "Upgave van syden dy tovoogd of vortedån wörden uut kategoryen.",
+       "rcfilters-filter-categorization-description": "Upgave van syden dee to-evoogd of vortedån wörden uut kategoryen.",
        "rcfilters-filter-logactions-label": "Eregistreerde aktys",
+       "rcfilters-filter-logactions-description": "Administrative handelingen, nye kontos, et vortdoon van syden, upstüren van bestanden…",
        "rcfilters-filtergroup-lastRevision": "Lätste versys",
        "rcfilters-filter-lastrevision-label": "Lätste versy",
        "rcfilters-filter-lastrevision-description": "Allinnig de lätste wysiging an een syde.",
-       "rcfilters-filter-previousrevision-label": "Nyt de lätste versy",
-       "rcfilters-filter-previousrevision-description": "Alle wysigingen dy nyt de \"lätste versy\" binnen.",
+       "rcfilters-filter-previousrevision-label": "Neet de lätste versy",
+       "rcfilters-filter-previousrevision-description": "Alle wysigingen dee neet de \"lätste versy\" binnen.",
        "rcfilters-view-namespaces-tooltip": "Filter resultåten up naamruumte",
        "rcfilters-view-tags-tooltip": "Filter resultåten döär gebruuk te maken van bewarkingsetiketten",
        "rcfilters-liveupdates-button": "Rechtstreakse aktualisering",
        "uploadbtn": "Bestand upstüren",
        "reuploaddesc": "Weerumme naor de opstuurzied",
        "upload-tryagain": "Bestaandsbeschrieving biewarken",
-       "uploadnologin": "Nyt an-emeld",
+       "uploadnologin": "Neet an-emelded",
        "uploadnologintext": "Je mutten $1 ween um bestaanden op te kunnen sturen.",
        "upload_directory_missing": "De inlaojmap veur bestaanden ($1) is vort en kon niet an-emaakt wörden deur de webserver.",
        "upload_directory_read_only": "Op t moment ku'j gien bestaanden opsturen vanwegen techniese problemen ($1).",
        "listgrouprights-addgroup-self-all": "Kan alle groepen bie de eigen gebruker doon",
        "listgrouprights-removegroup-self-all": "Kan alle groepen vortdoon van eigen gebruker",
        "trackingcategories": "Volgkategorieën",
-       "mailnologin": "Nyt an-emeld.",
+       "mailnologin": "Neet an-emelded.",
        "mailnologintext": "Je mutten [[Special:UserLogin|an-emeld]] ween en n geldig e-mailadres in \"[[Special:Preferences|mien veurkeuren]]\" invoeren um disse funksie te kunnen gebruken.",
        "emailuser": "Gebruker een bericht stüren",
        "emailuser-title-target": "Disse {{GENDER:$1|gebruker}} n bericht sturen",
        "watchlistfor2": "Veur $1 ($2)",
        "nowatchlist": "Gien artikels in volglieste.",
        "watchlistanontext": "$1 is verplicht um joew volglieste te bekieken of te wiezigen.",
-       "watchnologin": "Nyt an-emeld",
+       "watchnologin": "Neet an-emelded",
        "addwatch": "Op mien volglieste zetten",
        "addedwatchtext": "De zied \"[[:$1]]\" steet noen op joew [[Special:Watchlist|volglieste]].\nToekomstige wiezigingen op disse zied en de overlegzied zullen hier vermeld wörden.",
        "removewatch": "Van mien volglieste aofhaolen",
        "movepage-page-moved": "De zied $1 is herneumd naor $2.",
        "movepage-page-unmoved": "De zied $1 kon niet herneumd wörden naor $2.",
        "movepage-max-pages": "t Maximale antal automaties te herneumen ziejen is bereikt ({{PLURAL:$1|$1|$1}}).\nDe aandere ziejen wörden niet automaties herneumd.",
-       "movelogpage": "Herneumlogboek",
+       "movelogpage": "Hernöömlogbook",
        "movelogpagetext": "Hieronder steet n lieste mit ziejen die herneumd bin.",
        "movesubpage": "{{PLURAL:$1|Zied die deronder hank|Ziejen die deronder hangen}}",
        "movesubpagetext": "De {{PLURAL:$1|zied die onder disse zied hank|$1 ziejen die onder disse zied hangen}} vie'j hieronder.",
        "table_pager_empty": "Gien resultaoten",
        "autosumm-blank": "Zied leegemaakt",
        "autosumm-replace": "Tekste vervöngen deur '$1'",
-       "autoredircomment": "deurverwiezing naor [[$1]]",
+       "autoredircomment": "döärverwysing når [[$1]]",
        "autosumm-new": "Nieje zied: '$1'",
        "size-kilobytes": "$1 kB",
        "lag-warn-normal": "Wiezigingen die niejer bin as $1 {{PLURAL:$1|seconde|seconden}} staon misschien nog niet in de lieste.",
        "tag-filter": "[[Special:Tags|Etiketfilter]]:",
        "tag-filter-submit": "Filtreren",
        "tag-list-wrapper": "([[Special:Tags|Etiket{{PLURAL:$1||ten}}]]: $2)",
+       "tag-mw-new-redirect": "Nye döärverwysing",
+       "tag-mw-removed-redirect": "Döärverwysing vortedån",
        "tags-title": "Etiket",
        "tags-intro": "Op disse zied staon de etiketten waormee de programmatuur elke bewarking kan markeren, en de betekenisse dervan.",
        "tags-tag": "Etiketnaam",
        "htmlform-no": "Nee",
        "htmlform-yes": "Ja",
        "htmlform-chosen-placeholder": "Kies n opsie",
-       "logentry-delete-delete": "$1 hef de zied $3 {{GENDER:$2|vortedaon}}",
+       "logentry-delete-delete": "$1 hevt de syde $3 {{GENDER:$2|vortedån}}",
        "logentry-delete-restore": "$1 hef de zied $3 {{GENDER:$2|weerummezet}}",
        "logentry-delete-event": "$1 hef de zichtbaorheid van {{PLURAL:$5|n logboekregel|$5 logboekregels}} van $3 {{GENDER:$2|ewiezigd}}: $4",
        "logentry-delete-revision": "$1 hef de zichtbaorheid van {{PLURAL:$5|een versie|$5 versies}} van de zied $3 {{GENDER:$2|ewiezigd}}: $4",
        "revdelete-unrestricted": "hef beparkingen veur beheerders deraof ehaold",
        "logentry-block-block": "$1 {{GENDER:$2|hef}} {{GENDER:$4|$3}} eblokkeerd veur de duur van $5 $6",
        "logentry-suppress-block": "$1 {{GENDER:$2|hef}} {{GENDER:$4|$3}} eblokkeerd veur de duur van $5 $6",
-       "logentry-move-move": "$1 hef de zied $3 {{GENDER:$2|herneumd}} naor $4",
-       "logentry-move-move-noredirect": "$1 hef de zied $3 {{GENDER:$2|herneumd}} naor $4 zonder n deurverwiezing achter te laoten",
-       "logentry-move-move_redir": "$1 hef de zied $3 {{GENDER:$2|herneumd}} naor $4 over n deurverwiezing heer",
-       "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-move-move": "$1 hevt de syde $3 {{GENDER:$2|hernöömd}} når $4",
+       "logentry-move-move-noredirect": "$1 hevt de syde $3 {{GENDER:$2|hernöömd}} når $4 sunder een döärverwysing achter te låten",
+       "logentry-move-move_redir": "$1 hevt de syde $3 {{GENDER:$2|hernöömd}} når $4 oaver een döärverwysing hear",
+       "logentry-move-move_redir-noredirect": "$1 hevt de syde $3 {{GENDER:$2|hernöömd}} når $4 oaver een döärverwysing hear sunder een döärverwysing achter te låten",
        "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": "Gebruker $1 is {{GENDER:$2|an-emaakt}}",
index 5ee2d83..b37e66a 100644 (file)
        "recentchangeslinked-feed": "Verwante wijzigingen",
        "recentchangeslinked-toolbox": "Verwante wijzigingen",
        "recentchangeslinked-title": "Wijzigingen verwant aan \"$1\"",
-       "recentchangeslinked-summary": "Voer een paginanaam in om bewerkingen te zien van pagina's waarheen vanaf die pagina verwezen wordt of die ernaar verwijzen. (Om leden van een categorie te zien, voert u <kbd>Categorie:''Naam van categorie''</kbd> in.) Bewerkingen van pagina's op [[Special:Watchlist|uw volglijst]] worden <strong>vet</strong> weergegeven.",
+       "recentchangeslinked-summary": "Voer een paginanaam in om bewerkingen te zien van pagina's waarheen vanaf die pagina verwezen wordt of die ernaar verwijzen. (Om leden van een categorie te zien, voert u <kbd>{{ns:category}}:''Naam van categorie''</kbd> in.) Bewerkingen van pagina's op [[Special:Watchlist|uw volglijst]] worden <strong>vet</strong> weergegeven.",
        "recentchangeslinked-page": "Paginanaam:",
        "recentchangeslinked-to": "Wijzigingen aan pagina's met koppelingen naar deze pagina bekijken",
        "recentchanges-page-added-to-category": "[[:$1]] aan categorie toegevoegd",
        "deadendpages": "Pagina's zonder koppelingen",
        "deadendpagestext": "De onderstaande pagina's verwijzen niet naar andere pagina's in deze wiki.",
        "protectedpages": "Beveiligde pagina's",
+       "protectedpages-filters": "Filters:",
        "protectedpages-indef": "Alleen blokkades zonder vervaldatum",
        "protectedpages-summary": "Deze pagina bevat een lijst met beveiligde pagina's. Zie [[{{#special:ProtectedTitles}}|{{int:protectedtitles}}]] voor een lijst van pagina's die niet aangemaakt mogen worden.",
        "protectedpages-cascade": "Alleen beveiligingen met de cascade-optie",
index 5676834..fcd18d5 100644 (file)
        "mw-widgets-dateinput-placeholder-month": "ÅÅÅÅ-MM",
        "mw-widgets-titleinput-description-new-page": "sida finst ikkje enno",
        "mw-widgets-titleinput-description-redirect": "omdirigering til $1",
+       "mw-widgets-usersmultiselect-placeholder": "Legg til fleire …",
        "date-range-from": "Frå dato:",
        "date-range-to": "Til dato:",
        "randomrootpage": "Tilfeldig rotside",
index 44ec0a3..f11e4fd 100644 (file)
        "createacct-email-ph": "Entratz vòstra adreça de corrièr electronic",
        "createacct-another-email-ph": "Picar l'adreça de corrièr electronic",
        "createaccountmail": "Utilizar un senhal aleatòri temporari e lo mandar a l’adreça de corrièl especificada",
+       "createaccountmail-help": "Pòt s'utilizar per crear un compte per una autra persona sens conéisser lo mot de passa.",
        "createacct-realname": "Nom vertadièr (facultatiu)",
        "createacct-reason": "Motiu",
        "createacct-reason-ph": "Perqué creatz un autre compte",
        "wrongpassword": "Lo nom d'utilizaire o lo senhal es incorrècte.\nEnsajatz tornarmai.",
        "wrongpasswordempty": "Lo senhal picat èra void. Se vos plai, ensajatz tornarmai.",
        "passwordtooshort": "Vòstre senhal deu conténer al mens {{PLURAL:$1|1 caractèr|$1 caractèrs}}.",
+       "passwordtoolong": "Mots de passa pòdon pas aver mai de  {{PLURAL:$1|1 caractèr|$1 caractèrs}}.",
+       "passwordtoopopular": "Es pas possible d'utilizar mots de passa fòrça comuns. Vos cal causir un mot de passa mai malaisit  de deschifrar.",
        "password-name-match": "Vòstre senhal deu èsser diferent de vòstre nom d’utilizaire.",
        "password-login-forbidden": "L'usatge d'aquestes nom d'utilizaire e senhal es pas autorisat",
        "mailmypassword": "Reïnicializar lo senhal",
        "passwordreset-emailelement": "Utilizaire: \n$1\n\nSenhal temporari: \n$2",
        "passwordreset-emailsentemail": "Se aquesta adreça de corrièl es associada a vòstre compte, alara un corrièl de reïnicializacion de senhal serà mandat.",
        "passwordreset-emailsentusername": "Se i a una adreça de corrièr electronic associada a aqueste nom d’utilizaire, alara un corrièl de reïnicializacion senhal serà mandat.",
+       "passwordreset-nocaller": "Cal provesir un apelaire",
        "passwordreset-nosuchcaller": "L’apelant existís pas : $1",
+       "passwordreset-ignored": "Lo restabliment del mot de passa s'es pas plan realizat. Benlèu i aviá pas cap de fornidor configurat?",
        "passwordreset-invalidemail": "Adreça de corrièr electronic invalida",
+       "passwordreset-nodata": "Pas cap de nom d'usatgièr o d'adreça electronica foguèron provesits",
        "changeemail": "Cambiar o suprimir l'adreça electronica",
        "changeemail-header": "Completatz aqueste formulari per modificar vòstra adreça de corrièl. Se volètz suprimir l’associacion d’una adreça de corrièl amb vòstre compte, daissatz la novèla adreça de corrièl voida al moment de la somission del formulari.",
        "changeemail-no-info": "Vos cal èsser connectat per aver accès a aquesta pagina.",
        "changeemail-oldemail": "Adreça electronica actuala:",
        "changeemail-newemail": "Novela adreça electronica:",
+       "changeemail-newemail-help": "Vos cal daissar aquel camp void se volètz suprimir la vòstra adreça electronica. Mas, se suprimètz aquela adreça poiretz pas tornar inicializar lo mot de passa se l'avètz doblidat e recebretz pas de corrièrs electronics dempuèi aquel wiki.",
        "changeemail-none": "(pas cap)",
        "changeemail-password": "Vòstre senhal sus {{SITENAME}} :",
        "changeemail-submit": "Cambiar l'adreça electronica :",
        "changeemail-throttled": "Avètz fait tròp de temptativas de connexion.\nEsperatz $1 abans d’ensajar tornarmai.",
+       "changeemail-nochange": "Vos cal picar una novèla adreça electronica, diferenta de la precedenta.",
        "resettokens": "Reïnicializar los getons",
        "resettokens-text": "Aici, podètz reïnicializar los getons que permeton d’accedir a d'unas donadas privadas associadas a vòstre compte.\n\nLo vos caldriá far se las avètz partejats accidentalament amb qualqu'un o se vòstre compte es estat compromés.",
        "resettokens-no-tokens": "I a pas cap de geton de reïnicializar.",
        "anoneditwarning": "<strong>Atencion :<strong> sètz pas connectat.\nVòstra adreça IP serà visibla per tot lo monde se fasètz de modificacions. Se <strong>[$1 vos connectatz]</strong> o <strong>[$2 creatz un compte]</strong>, vòstras modificacions seràn atribuidas a vòstre nom d’utilizaire, entre autres avantatges.",
        "anonpreviewwarning": "''Sètz pas identificat. Salvar enregistrarà vòstra adreça IP dins l’istoric de las modificacions de la pagina.''",
        "missingsummary": "'''Atencion :''' avètz pas modificat lo resumit de vòstra modificacion. Se clicatz tornarmai sul boton « Salvar », lo salvament serà fait sens avertiment mai.",
+       "selfredirect": "<strong>Atencion:</strong> Sètz a redirigir aquela pagina cap a se meteissa.\nPodètz aver especificat un faus objectiu per la redireccion, o benlèu avètz modificar una pagina incorrècta.\nSe tornatz faire un clic \"$1\" , la redireccion serà çaquelà creada.",
        "missingcommenttext": "Mercé de metre un comentari.",
        "missingcommentheader": "<strong>Rapèl :</strong> Avètz pas provesit cap de subjècte per aqueste comentari.\nSe clicatz tornamai sus « {{int:Savearticle}} », vòstra modificacion serà enregistrada sens subjècte.",
        "summary-preview": "Apercebut del resumit de modificacion :",
        "subject-preview": "Apercebut del subjècte :",
+       "previewerrortext": "S'es produsida una error quand ensagèretz  de previsualizar los cambiaments.",
        "blockedtitle": "L'utilizaire es blocat",
        "blockedtext": "'''Vòstre compte d'utilizaire o vòstra adreça IP es estat blocat'''\n\nLo blocatge es estat efectuat per $1.\nLa rason invocada es la seguenta : ''$2''.\n\n* Començament del blocatge : $8\n* Expiracion del blocatge : $6\n* Compte blocat : $7.\n\nPodètz contactar $1 o un autre [[{{MediaWiki:Grouppage-sysop}}|administrator]] per ne discutir.\nPodètz pas utilizar la foncion « Mandar un corrièr electronic a aqueste utilizaire » que se una adreça de corrièr valida es especificada dins vòstras [[Special:Preferences|preferéncias]].\nVòstra adreça IP actuala es $3 e vòstre identificant de blocatge es #$5.\nIncluissètz aquesta adreça dins tota requèsta.",
        "autoblockedtext": "Vòstra adreça IP es estada blocada automaticament perque es estada utilizada per un autre utilizaire, ele-meteis blocat per $1.\nLa rason invocadaa es :\n\n:''$2''\n\n* Començament del blocatge : $8\n* Expiracion del blocatge : $6\n* Compte blocat : $7\n\nPodètz contactar $1 o un dels autres [[{{MediaWiki:Grouppage-sysop}}|administrators]] per discutir d'aqueste blocatge.\n\nNotatz que podètz pas utilizar la foncionalitat \"Mandar un messatge a aqueste utilizaire\" tant qu'auretz pas  una adreça e-mail enregistrada dins vòstras [[Special:Preferences|preferéncias]] e tant que seretz pas blocat per son utilizacion.\n\nVòstra adreça IP actuala es $3, e lo numèro de blocatge es $5.\nPrecisatz aquestas indicacions dins totas las requèstas que faretz.",
+       "systemblockedtext": "Lo vòstre nom d'usatgièr o adreça IP foguèt estat blocat automaticament pel MediaWiki.\nLo motiu balhat es:\n\n:<em>$2</em>\n\n* Començament del blocatge: $8\n* Fin del delai de blocatge: $6\n* Element pertocat: $7\n\nLa vòstra adreça IP actuala es $3.\nApondètz las donadas de mai amont per cada demanda  que faretz.",
        "blockednoreason": "Cap de rason balhada",
        "whitelistedittext": "Vos cal èsser $1 per modificar las paginas.",
        "confirmedittext": "Vos cal confirmar vòstra adreça electronica abans de modificar l'enciclopèdia. Picatz e validatz vòstra adreça electronica amb l'ajuda de la pagina [[Special:Preferences|preferéncias]].",
        "yourtext": "Vòstre tèxte",
        "storedversion": "Version enregistrada",
        "editingold": "'''Atencion : sètz a modificar una version obsolèta d'aquesta pagina. Se salvatz, totas las modificacions efectuadas dempuèi aquesta version seràn perdudas.'''",
+       "unicode-support-fail": "Sembla que lo vòstre navigador es pas compatible amb Unicode. Aquò es necessari per modificar las paginas, la vòstra edicion foguèt pas salvagardada.",
        "yourdiff": "Diferéncias",
        "copyrightwarning": "Totas las contribucions a {{SITENAME}} son consideradas coma publicadas jols tèrmes de la $2 (vejatz $1 per mai de detalhs). Se desiratz pas que vòstres escrits sián modificats e distribuits a volontat, mercés de los sometre pas aicí.<br /> Nos prometètz tanben qu'avètz escrit aquò vos-meteis, o que l’avètz copiat d’una font provenent del domeni public, o d’una ressorsa liura.'''UTILIZETZ PAS DE TRABALHS JOS COPYRIGHT SENS AUTORIZACION EXPRÈSSA !'''",
        "copyrightwarning2": "Totas las contribucions a {{SITENAME}} pòdon èsser modificadas o suprimidas per d’autres utilizaires. Se desiratz pas que vòstres escrits sián modificats e distribuits a volontat, mercés de los sometre pas aicí.<br /> Tanben nos prometètz qu'avètz escrit aquò vos-meteis, o que l’avètz copiat d’una font provenent del domeni public, o d’una ressorsa liura. (vejatz $1 per mai de detalhs). '''UTILIZETZ PAS DE TRABALHS JOS COPYRIGHT SENS AUTORIZACION EXPRÈSSA !'''",
+       "editpage-cannot-use-custom-model": "Lo modèl de contengut d'aquela pagina pòt pas èsser cambiat.",
        "longpageerror": "'''ERROR : Lo tèxte qu'avètz somés fa {{PLURAL:$1|un Kio|$1 Kio}}, çò que depassa lo limit fixat a {{PLURAL:$2|un Kio|$2 Kio}}.'''. Pòt pas èsser salvat.",
        "readonlywarning": "<strong>AVERTIMENT : La basa de donadas es estada verrolhada per d'operacions de mantenença. Doncas, poiretz pas publicar vòstras modificacions pel moment.</strong>\nL’administrator sistèma qu'an verrolhada la basa de donadas a donat l’explicacion seguenta : $1",
        "protectedpagewarning": "'''AVERTIMENT : Aquesta pagina es protegida. Sols los utilizaires qu'an l'estatut d'administrator la p�don modificar. ''' La darri�ra entrada del jornal es afichada �aij�s per refer�ncia :",
        "permissionserrors": "Error de permission",
        "permissionserrorstext": "Avètz pas la permission d’efectuar l’operacion demandada per {{PLURAL:$1|la rason seguenta|las rasons seguentas}} :",
        "permissionserrorstext-withaction": "Sètz pas autorizat(ada) a $2, per {{PLURAL:$1|la rason seguenta|las rasons seguentas}} :",
+       "contentmodelediterror": "Podètz pas modificar aquela revision perque lo sieu modèl de contengut es <code>$1</code>, qu'es diferent del modèl de contengut actual de la pagina <code>$2</code>.",
        "recreate-moveddeleted-warn": "'''Atencion : sètz a tornar crear una pagina qu'es estada suprimida precedentament.'''\n\nDemandatz-vos s'es vertadièrament apropriat de contunhar de l’editar.\nL’istoric de las supressions e dels cambiaments de nom es afichat çaijós :",
        "moveddeleted-notice": "Aquesta pagina es estada suprimida.\nLo jornal de las supressions, de las proteccions e dels desplaçaments de la pagina es afichat çaijós per referéncia.",
+       "moveddeleted-notice-recent": "Desolat, aquela pagina foguèt recentament suprimida (en las darrièras 24 oras).\nPodètz consultar lo registre de las supressions, proteccions e dels renomenatges de la pagina çai-jos.",
        "log-fulllog": "Veire lo jornal complet",
        "edit-hook-aborted": "Modificacion fracassada per croquet.\nCap d'explicacion pas balhada.",
        "edit-gone-missing": "A pas pogut metre a jorn la pagina.\nSembla que siá estada suprimida.",
        "postedit-confirmation-created": "La pagina es estada creada.",
        "postedit-confirmation-restored": "La pagina es estada restablida.",
        "postedit-confirmation-saved": "Vòstra modificacion es estada salvada.",
+       "postedit-confirmation-published": "La vòstra modificacion foguèt publicada.",
        "edit-already-exists": "La pagina novèla a pogut èsser creada .\nExistís ja.",
        "defaultmessagetext": "Messatge per defaut",
        "content-failed-to-parse": "Fracàs de l'analisi del contengut de $2 pel modèl $1: $3",
        "invalid-content-data": "Donadas del contengut invalidas",
        "content-not-allowed-here": "Lo contengut \"$1\" es pas autorizat sus la pagina [[$2]]",
        "editwarning-warning": "Quitar aquesta pagina vos farà pèrdre totas las modificacions qu'avètz faitas.\nSe sètz connectat, podètz desactivar aqueste avertiment dins la seccion « {{int:prefs-editing}} » de vòstras preferéncias.",
+       "editpage-invalidcontentmodel-title": "Modèl de contengut pas permés",
+       "editpage-invalidcontentmodel-text": "Lo modèl de contengut «$1» es pas permés.",
        "editpage-notsupportedcontentformat-title": "Format de contengut pas pres en carga",
        "editpage-notsupportedcontentformat-text": "Lo format de contengut $1 es pas pres en carga pel modèl de contengut $2 .",
        "content-model-wikitext": "wikitèxte",
        "expansion-depth-exceeded-warning": "Pagina depassant la prigondor d'espandiment",
        "parser-unstrip-loop-warning": "Bocla pas desmontabla detectada",
        "unstrip-depth-warning": "Limit de recursion pas desmontable depassat ($1)",
+       "unstrip-depth-category": "Paginas que la limita de prigondor de desvolopament i  es despassada",
+       "unstrip-size-warning": "Limita de mesura de desvolopament despassada ($1)",
+       "unstrip-size-category": "Paginas que la limita de la mesura de desvolopament es despassada",
        "converter-manual-rule-error": "Error detectada dins la règla manuala de conversion de lenga",
        "undo-success": "Aquesta modificacion va èsser desfaita. Confirmatz los cambiaments (visibles en bas d'aquesta pagina), puèi salvatz se sètz d’acòrdi. Mercés de motivar l’anullacion dins la bóstia de resumit.",
        "undo-failure": "Aquesta modificacion a pas pogut èsser desfaita a causa de conflictes amb de modificacions intermediàrias.",
        "mergehistory-fail-bad-timestamp": "L’orodatatge es pas valid.",
        "mergehistory-fail-invalid-source": "La pagina font es pas valida.",
        "mergehistory-fail-invalid-dest": "La pagina de destinacion es invalida",
+       "mergehistory-fail-no-change": "La fusion d'istoric a pas fusionat cap de revision. Tornatz  verificar la pagina e los paramètres de temps.",
+       "mergehistory-fail-permission": "Avètz pas de permissions sufisentas per fusionar l'istoric.",
+       "mergehistory-fail-self-merge": "Las paginas d'origina e de destinacion son las meteissas",
+       "mergehistory-fail-timestamps-overlap": "Las revisions d'origina se superpausan o seguisson las de destinacion.",
        "mergehistory-fail-toobig": "Impossible d’efectuar la fusion de l’istoric perque un nombre de {{PLURAL:$1|revisions}} superior al limit de $1 deuriá èsser desplaçat.",
        "mergehistory-no-source": "La pagina d'origina $1 existís pas.",
        "mergehistory-no-destination": "La pagina de destinacion $1 existís pas.",
        "diff-multi-sameuser": "({{PLURAL:$1|Una revision intermediària pel meteis utilizaire pas afichada|$1 revisions intermediàrias pel meteis utilizaire pas afichadas}})",
        "diff-multi-otherusers": "({{PLURAL:$1|Una revision intermediària|$1 revisions intermediàrias}} per {{PLURAL:$2|un autre utilizaire|$2 utilizaires}} pas {{PLURAL:$1|afichada|afichadas}})",
        "diff-multi-manyusers": "({{PLURAL:$1|Una revision intermediària amagada|$1 revisions intermediàrias amagadas}}) per ({{PLURAL:$2|un utilizaire pas afichada|$2 utilizaires pas afichadas}})",
+       "diff-paragraph-moved-tonew": "Lo paragraf foguèt desplaçat. Fasètz un clic per anar a l'emplaçament nòu.",
+       "diff-paragraph-moved-toold": "Lo paragraf foguèt desplaçat. Fasètz un clic par anar a l'emplaçament ancian.",
        "difference-missing-revision": "{{PLURAL:$2|Una revision|$2 revisions}} d'aquesta diferéncia ($1) {{PLURAL:$2|es pas estada trobada|son pas estadas trobadas}}.\n\nAquò se produtz en general en seguent un ligam de diferéncia obsolèta cap a una pagina qu'es estada suprimada.\nPodètz trobar de detalhs dins lo [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} jornal de las supressions].",
        "searchresults": "Resultats de la recèrca",
        "searchresults-title": "Resultats de la recèrca per « $1 »",
        "search-category": "(categoria $1)",
        "search-file-match": "(correspond al contengut del fichièr)",
        "search-suggest": "Avètz volgut dire : $1",
+       "search-rewritten": "S’i mòstran los resultats de $1. Cercatz «$2» en luòc d'aquò.",
        "search-interwiki-caption": "Resultats dels projèctes fraires",
        "search-interwiki-default": "Resultats de $1 :",
        "search-interwiki-more": "(mai)",
        "search-external": "Recèrca extèrna",
        "searchdisabled": "La recèrca sus {{SITENAME}} es desactivada.\nEn esperant la reactivacion, podètz efectuar una recèrca via Google.\nAtencion, lor indexacion de contengut {{SITENAME}} benlèu es pas a jorn.",
        "search-error": "Una error s'es produita en recercant : $1",
+       "search-warning": "Una error s'es produsida en cercant : $1",
        "preferences": "Preferéncias",
        "mypreferences": "Preferéncias",
        "prefs-edits": "Nombre d’edicions :",
        "stub-threshold-disabled": "Desactivat",
        "recentchangesdays": "Nombre de jorns d'afichar dins los darrièrs cambiaments :",
        "recentchangesdays-max": "(maximum $1 {{PLURAL:$1|jorn|jorns}})",
-       "recentchangescount": "Nombre de modificacions d'afichar per defaut :",
-       "prefs-help-recentchangescount": "Aquò inclutz las modificacions recentas, las paginas d’istorics e los jornals.",
+       "recentchangescount": "Nombre de modificacions d'afichar per defauta dins los cambiaments recents, los istorics e los logs :",
+       "prefs-help-recentchangescount": "Nombre maximum : 1000",
        "prefs-help-watchlist-token2": "Aquí la clau secreta del flux Web de vòstra lista de seguiment.\nTota persona que la coneis poirà legir vòstra lista de seguiment, doncas, la comuniquetz pas.\nSe necessari, [[Special:ResetTokens|clicatz aicí per la reïnicializar]].",
        "savedprefs": "Las preferéncias son estadas salvadas.",
        "savedrights": "Los dreits d'utilizaire de {{GENDER:$1|$1}} son estats enregistrats.",
        "timezoneregion-indian": "Ocean Indian",
        "timezoneregion-pacific": "Ocean Pacific",
        "allowemail": "Autorizar los autres utilizaires a me mandar de corrièls",
+       "email-allow-new-users-label": "Autorizar corrièr electronic d'utilizaires nòus",
        "prefs-searchoptions": "Recèrca",
        "prefs-namespaces": "Noms d’espacis",
        "default": "defaut",
index 1f4f82c..d856230 100644 (file)
        "deleting-backlinks-warning": "گواښنه:''' دا مخ چې تاسې يې ړنگوی د [[Special:WhatLinksHere/{{FULLPAGENAME}}|نورو مخونو]] سره تړلی او يا هم په نورو مخونو کې نغاړل شوی دی.",
        "rollbacklink": "په شابېول",
        "rollbacklinkcount": "$1 {{PLURAL:$1|سمون|سمونونه}} پرشابېول",
+       "rollbacklinkcount-morethan": "تر $1 زيات {{PLURAL:$1|بدلون|بدلونونه}} پرشا بوځي",
        "editcomment": "د سمون لنډيز دا وو: \"''$1''\".",
        "changecontentmodel-title-label": "مخ سرليک",
        "changecontentmodel-model-label": "د نوي مېنځپانگې موډل",
        "limitreport-expansiondepth-value": "$1/$2",
        "limitreport-expensivefunctioncount": "د قیمتي پارسير فعالیت شمیرې",
        "limitreport-expensivefunctioncount-value": "$1/$2",
+       "limitreport-unstrip-size-value": "$1/$2 {{PLURAL:$2|ټکۍ|ټکي}}",
        "expandtemplates": "کينډۍ غځول",
        "expand_templates_intro": "په دا ځانګړي مخ کي متن پاڼه ترلاسه کیږي کوم چي په ټول ډوله مخونو کي کارول کیږي دلته دا مخ بيا بیا وده کوي. د تحلیل دندو لکه <code><nowiki>{{</nowiki>#language:…}}</code> او متغیرونه لکه <code><nowiki>{{</nowiki>CURRENTDAY}}</code> هم سره نښلوي — په واقعیت کې، د ډلو دننه هر څه. دا خپله د ميډياويکي په اړونده مرحله کولو سره ترسره کيږي.",
        "expand_templates_title": "د موزوع سرليک، د {{FULLPAGENAME}} لپاره او نور:",
        "restrictionsfield-badip": "ناباوره آي پي  آدرس او حدود د : $1",
        "restrictionsfield-label": "اجازه ورکړل شوي آي پي حدودونه:",
        "restrictionsfield-help": "په هر کرښه کې د اي پي پته یا د سینیر رینټ داخل کړئ. د هر شی فعالولو لپاره دا ارزښت وکاروئ: <code>0.0.0.0/0</code><br /><code>::/0</code>",
+       "edit-error-short": "تيروتنه: $1",
+       "edit-error-long": "تيروتنې:$1",
        "revid": "بیاکتنه $1",
        "pageid": "د مخ پېژند$1",
        "rawhtml-notallowed": "لیبلونه &lt;html&gt; د منظمو ليکنو څخه بهر نشي کارول کیدی.",
index df49ffe..e8170dd 100644 (file)
        "prefs-dateformat": "Formato de data",
        "prefs-timeoffset": "Desvio horário",
        "prefs-advancedediting": "Opções gerais",
+       "prefs-developertools": "Ferramentas de desenvolvimento",
        "prefs-editor": "Editor",
        "prefs-preview": "Antevisão",
        "prefs-advancedrc": "Opções avançadas",
index 29869a1..4d71e75 100644 (file)
                        "Gombang",
                        "Trizek (WMF)",
                        "Acamicamacaraca",
-                       "Avatar6"
+                       "Avatar6",
+                       "Akapochtli"
                ]
        },
        "sidebar": "{{notranslate}}",
        "botpasswords-existing": "Form section label for the part of the form listing the user's existing bot passwords.",
        "botpasswords-createnew": "Form section label for the part of the form related to creating a new bot password.",
        "botpasswords-editexisting": "Form section label for the part of the form related to editing an existing bot password.",
+       "botpasswords-label-needsreset": "Indicator for when an existing bot password is invalid and needs to be reset.",
        "botpasswords-label-appid": "Form field label for the \"bot name\", internally known as the \"application ID\".",
        "botpasswords-label-create": "Button label for the button to create a new bot password.\n{{Identical|Create}}",
        "botpasswords-label-update": "Button label for the button to save changes to a bot password.\n{{Identical|Update}}",
        "botpasswords-restriction-failed": "Error message when login is rejected because the configured restrictions were not satisfied.",
        "botpasswords-invalid-name": "Error message when a username lacking the separator character is passed to BotPassword. Parameters:\n* $1 - The separator character.",
        "botpasswords-not-exist": "Error message when a username exists but does not a bot password for the given \"bot name\". Parameters:\n* $1 - username\n* $2 - bot name",
+       "botpasswords-needs-reset": "Error message when a bot password exists but needs to be reset. Parameters:\n* $1 - username\n* $2 - bot name",
        "resetpass_forbidden": "Used as error message in changing password. Maybe the external auth plugin won't allow local password changes.",
        "resetpass_forbidden-reason": "Like {{msg-mw|resetpass_forbidden}} but the auth provider gave a reason.\n\nParameters:\n* $1 - reason given by auth provider",
        "resetpass-no-info": "Error message for [[Special:ChangePassword]].\n\nParameters:\n* $1 (unused) - a link to [[Special:UserLogin]] with {{msg-mw|loginreqlink}} as link description",
        "prefs-watchlist-edits": "Used in [[Special:Preferences]], tab \"Watchlist\".",
        "prefs-watchlist-edits-max": "Shown as hint in [[Special:Preferences]], tab \"Watchlist\"",
        "prefs-watchlist-token": "Used in [[Special:Preferences]], tab Watchlist.",
+       "prefs-watchlist-managetokens": "Label for the button to see and reset the user's private tokens",
        "prefs-misc": "Tab used on the [[Special:Preferences|user preferences]] special page.",
        "prefs-resetpass": "Button on user data tab in user preferences. When you click the button you go to the special page [[Special:ResetPass]].\n\n{{Identical|Change password}}",
        "prefs-changeemail": "Link on [[Special:Preferences]] to [[Special:ChangeEmail]]. [[Special:ChangeEmail]] also allows removing email address. \n\nSee also:\n* {{msg-mw|prefs-help-email-required|help}}\n* {{msg-mw|prefs-help-email|help}}\n* {{msg-mw|prefs-help-email-others|help}}\n* {{msg-mw|prefs-setemail|link title}}",
        "recentchangescount": "Used in [[Special:Preferences]], tab \"Recent changes\".",
        "prefs-help-recentchangescount": "Used in [[Special:Preferences]], tab \"Recent changes\".",
        "prefs-help-watchlist-token2": "Used in [[Special:Preferences]], tab Watchlist. (Formerly in {{msg-mw|prefs-help-watchlist-token}}.)",
+       "prefs-help-tokenmanagement": "Used in [[Special:Preferences]], Watchlist tab.",
        "savedprefs": "This message appears after saving changes to your user preferences.",
        "savedrights": "This message appears after saving the user groups on [[Special:UserRights]].\n* $1 - The user name of the user which groups was saved.",
        "timezonelegend": "{{Identical|Time zone}}",
index 6d1dcd0..b89d6f8 100644 (file)
        "minoredit": "Midà be bagatellas",
        "watchthis": "Observar quest artitgel",
        "savearticle": "Memorisar la pagina",
+       "publishchanges": "Publitgar midadas",
        "preview": "Prevista",
        "showpreview": "Mussar prevista",
        "showdiff": "Mussar midadas",
index b2fa321..fc930a1 100644 (file)
        "rcfilters-filter-bots-description": "Modificări făcute cu unelte automate.",
        "rcfilters-filter-humans-label": "Om (nu robot)",
        "rcfilters-filter-humans-description": "Modificări făcute de oameni.",
-       "rcfilters-filtergroup-reviewstatus": "Revizuiți starea",
+       "rcfilters-filtergroup-reviewstatus": "Statutul reviziei",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "Nepatrulate",
        "rcfilters-filtergroup-significance": "Semnificație",
        "rcfilters-filter-minor-label": "Modificări minore",
        "rcfilters-filter-watchlistactivity-seen-label": "Schimbări văzute",
        "rcfilters-filter-watchlistactivity-seen-description": "Modificările paginilor pe care le-ați vizitat de la efectuarea schimbărilor.",
        "rcfilters-filtergroup-changetype": "Tipul modificării",
-       "rcfilters-filter-pageedits-label": "Editări ale paginii",
+       "rcfilters-filter-pageedits-label": "Modificări în pagini",
        "rcfilters-filter-pageedits-description": "Editări ale conținutului wiki, discuții, descrieri de categorii…",
        "rcfilters-filter-newpages-label": "Creare de pagini",
        "rcfilters-filter-newpages-description": "Modificări care au dus la crearea de pagini noi.",
index 1eeeebb..841d011 100644 (file)
        "tog-watchlisthideminor": "Скрывать малые правки из списка наблюдения",
        "tog-watchlisthideliu": "Скрывать правки представившихся участников из списка наблюдения",
        "tog-watchlistreloadautomatically": "Обновлять список наблюдения автоматически всякий раз, когда изменяется фильтр (требуется JavaScript)",
-       "tog-watchlistunwatchlinks": "Ð\94обавиÑ\82Ñ\8c Ð² Ñ\81пиÑ\81ок Ð½Ð°Ð±Ð»Ñ\8eдениÑ\8f Ð¿Ñ\80Ñ\8fмÑ\8bе Ñ\81Ñ\81Ñ\8bлки Ð´Ð»Ñ\8f Ð¸Ñ\81клÑ\8eÑ\87ениÑ\8f Ð·Ð°Ð¿Ð¸Ñ\81ей (требуется JavaScript)",
+       "tog-watchlistunwatchlinks": "Ð\94обавиÑ\82Ñ\8c Ð¿Ñ\80Ñ\8fмÑ\8bе Ð¼Ð°Ñ\80кеÑ\80Ñ\8b Ð´Ð»Ñ\8f Ð²ÐºÐ»Ñ\8eÑ\87ениÑ\8f/иÑ\81клÑ\8eÑ\87ениÑ\8f Ð¸Ð· Ñ\81пиÑ\81ка Ð½Ð°Ð±Ð»Ñ\8eдениÑ\8f ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) Ð´Ð»Ñ\8f Ð½Ð°Ð±Ð»Ñ\8eдаемÑ\8bÑ\85 Ñ\81Ñ\82Ñ\80аниÑ\86 Ñ\81 Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8fми (длÑ\8f Ð¿ÐµÑ\80еклÑ\8eÑ\87ениÑ\8f Ñ\84Ñ\83нкÑ\86ий требуется JavaScript)",
        "tog-watchlisthideanons": "Скрывать правки анонимных участников из списка наблюдения",
        "tog-watchlisthidepatrolled": "Скрывать отпатрулированные правки из списка наблюдения",
        "tog-watchlisthidecategorization": "Скрывать категоризацию страниц",
        "savearticle-start": "Сохранить страницу…",
        "savechanges-start": "Сохранить изменения…",
        "publishpage-start": "Опубликовать страницу…",
-       "publishchanges-start": "Ð\9eпÑ\83бликоваÑ\82Ñ\8c Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f…",
+       "publishchanges-start": "Ð\97апиÑ\81аÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83…",
        "preview": "Предпросмотр",
        "showpreview": "Предварительный просмотр",
        "showdiff": "Внесённые изменения",
        "prefs-dateformat": "Формат даты",
        "prefs-timeoffset": "Смещение поясного времени",
        "prefs-advancedediting": "Общие параметры",
+       "prefs-developertools": "Инструменты разработчика",
        "prefs-editor": "Редактор",
        "prefs-preview": "Предварительный просмотр",
        "prefs-advancedrc": "Расширенные настройки",
        "action-undelete": "восстановление страниц",
        "action-suppressrevision": "просмотр и восстановление скрытых версий страниц",
        "action-suppressionlog": "просмотр частного журнала",
-       "action-block": "блокировку участника",
+       "action-block": "блокировка участника",
        "action-protect": "изменение уровня защиты этой страницы",
        "action-rollback": "быстрый откат изменений",
        "action-import": "импорт страниц из другой вики",
        "recentchangeslinked-feed": "Связанные правки",
        "recentchangeslinked-toolbox": "Связанные правки",
        "recentchangeslinked-title": "Связанные правки для «$1»",
-       "recentchangeslinked-summary": "Введите имя страницы, чтобы увидеть изменения на страницах, ссылающихся на эту страницу или, наоборот, c неё. (Чтобы увидеть членов категории, введите Category:Название категории). Изменения на страницах [[Special:Watchlist|вашего списка наблюдения]] отмечены <strong>жирным шрифтом</strong>.",
+       "recentchangeslinked-summary": "Введите имя страницы, чтобы увидеть изменения на страницах, ссылающихся на эту страницу или, наоборот, c неё. (Чтобы увидеть членов категории, введите {{ns:category}}:Название категории). Изменения на страницах [[Special:Watchlist|вашего списка наблюдения]] отмечены <strong>жирным шрифтом</strong>.",
        "recentchangeslinked-page": "Название страницы:",
        "recentchangeslinked-to": "Наоборот, показать изменения на страницах, которые ссылаются на указанную страницу",
        "recentchanges-page-added-to-category": "[[:$1]] добавлена в категорию",
        "imagelinks": "Использование файла",
        "linkstoimage": "{{PLURAL:$1|Следующая $1 страница ссылается|Следующие $1 страницы ссылаются|Следующие $1 страниц ссылаются}} на данный файл:",
        "linkstoimage-more": "Более $1 {{PLURAL:$1|страницы|страниц}} ссылаются на этот файл.\nВ данном списке {{PLURAL:$1|представлена только $1 ссылка|представлены только $1 ссылки|представлены только $1 ссылок}} на этот файл.\nДоступен также [[Special:WhatLinksHere/$2|полный список]].",
-       "nolinkstoimage": "Нет страниц, ссылающихся на данный файл.",
+       "nolinkstoimage": "Нет страниц, включающих этот файл.",
        "morelinkstoimage": "Просмотреть [[Special:WhatLinksHere/$1|остальные ссылки]] на этот файл.",
        "linkstoimage-redirect": "$1 (файловое перенаправление) $2",
        "duplicatesoffile": "{{PLURAL:$1|Следующий файл является дубликатом|Следующие $1 файла являются дубликатами|Следующие $1 файлов являются дубликатами}} этого файла ([[Special:FileDuplicateSearch/$2|подробности]]):",
index c6b9521..65f27f6 100644 (file)
@@ -16,7 +16,8 @@
                        "MtDu",
                        "Manik Soren",
                        "Ramjit Tudu",
-                       "R Ashwani Banjan Murmu"
+                       "R Ashwani Banjan Murmu",
+                       "Fagunkoyel Hansdah"
                ]
        },
        "tog-underline": "ᱡᱚᱱᱚᱲ ᱞᱟᱛᱟᱨᱨᱮ ᱫᱟᱜᱽ ᱩᱫᱩᱜᱽᱢᱮ:",
        "pool-timeout": "Somoy paromena cạbi lạgit́te tạṅgi hoyoḱkana",
        "pool-queuefull": "Pool queue is full",
        "pool-errorunknown": "ᱵᱟᱝ ᱵᱟᱰᱟᱭ ᱦᱩᱲᱟᱹᱜ",
-       "aboutsite": "ᱵᱟᱵᱚᱛ {{SITENAME}}",
+       "aboutsite": "{{SITENAME}} ᱵᱟᱵᱚᱛ",
        "aboutpage": "Project: ᱵᱟᱵᱚᱛ",
        "copyright": "ᱩᱱᱩᱫᱩᱜ ᱫᱚ ᱧᱟᱢᱚᱜ-ᱟ $1 ᱞᱮᱠᱟᱛᱮ  ᱵᱟᱝᱠᱷᱟᱱ ᱚᱞ ᱛᱟᱦᱮᱱᱟ",
        "copyrightpage": "{{ns:project}}: ᱮᱠᱛᱤᱭᱟᱨ",
        "listfiles_date": "ᱢᱟᱹᱦᱤᱛ",
        "listfiles_name": "ᱧᱩᱛᱩᱢ",
        "listfiles_user": "ᱵᱮᱵᱦᱟᱨᱤᱡ",
+       "listfiles-latestversion-yes": "ᱦᱮᱸ",
+       "listfiles-latestversion-no": "ᱵᱟᱝ",
        "file-anchor-link": "ᱨᱮᱫ",
        "filehist": "ᱨᱮᱫ ᱨᱮᱭᱟᱜ ᱱᱟᱜᱟᱢ",
        "filehist-help": "ᱚᱠᱛᱚ ᱨᱮ ᱞᱤᱱ ᱢᱮ/ᱚᱠᱛᱚ ᱨᱮ ᱨᱮᱫ ᱧᱮᱞ ᱞᱟᱹᱜᱤᱛ ᱞᱤᱱ ᱢᱮ",
        "filehist-nothumb": "ᱵᱟᱹᱱᱩᱜ-ᱟ ᱴᱤᱯ-ᱨᱟᱢᱟ",
        "filehist-user": "ᱵᱮᱵᱦᱟᱨᱤᱡ",
        "filehist-dimensions": "ᱡᱚᱠᱷᱟ",
+       "filehist-filesize": "ᱨᱮᱫ ᱥᱟᱭᱤᱡᱽ",
        "filehist-comment": "ᱠᱟᱛᱷᱟ",
        "imagelinks": "ᱯᱷᱟᱭᱤᱞ ᱵᱮᱣᱦᱟᱨ",
        "linkstoimage": "ᱞᱟᱛᱟᱨ ᱨᱮᱭᱟᱜ {{PLURAL:$1|ᱥᱟᱦᱴᱟ ᱡᱚᱱᱚᱲᱠᱚ|$1 ᱥᱟᱦᱴᱟᱠᱚ ᱡᱚᱱᱚᱲ}} ᱱᱤᱭᱟᱹ ᱨᱮᱫ ᱨᱮ:",
        "sharedupload-desc-here": "ᱱᱚᱣᱟ ᱨᱮᱫ ᱫᱚ ᱱᱚᱸᱰᱮ ᱠᱷᱚᱱ $1 ᱟᱨ ᱯᱟᱥᱮᱡ ᱮᱴᱟᱜ-ᱟ ᱯᱚᱨᱡᱮᱠᱴ ᱨᱮᱦᱚᱸ ᱵᱮᱵᱦᱟᱨᱚᱜ ᱠᱟᱱᱟ᱾\nᱱᱚᱣᱟ ᱨᱮᱭᱟ ᱯᱟᱥᱱᱟᱣ ᱠᱟᱛᱷᱟ [$2 ᱨᱮᱫ ᱯᱟᱥᱱᱟᱣ ᱥᱟᱦᱴᱟ] ᱞᱟᱛᱟᱨᱨᱮ ᱮᱢ ᱮᱱᱟ᱾",
        "filepage-nofile": "ᱱᱚᱶᱟ ᱧᱩᱛᱩᱢᱟᱜ ᱨᱮᱫ ᱵᱟᱹᱱᱩᱜ-ᱟ ᱾",
        "upload-disallowed-here": "ᱟᱢᱫᱚ ᱱᱚᱣᱟ ᱨᱮᱫ ᱪᱮᱛᱟᱱ ᱵᱟᱢ ᱚᱞ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ᱾",
+       "filerevert-comment": "ᱚᱡᱮ:",
        "mimesearch": "MIME ᱥᱮᱸᱫᱽᱨᱟ",
        "randompage": "ᱡᱚᱲᱟᱣ ᱥᱟᱦᱴᱟ",
        "statistics": "ᱦᱟᱞᱚᱛ",
        "prefixindex": "ᱡᱚᱛᱚ ᱥᱟᱦᱴᱟᱠᱚ prefix ᱥᱟᱶ",
        "shortpages": "ᱦᱩᱰᱤᱧ ᱥᱟᱦᱴᱟᱠᱚ",
        "longpages": "ᱡᱤᱞᱤᱧ ᱥᱟᱦᱴᱟᱠᱚ",
+       "protectedpages-page": "ᱥᱟᱦᱴᱟ",
+       "protectedpages-reason": "ᱚᱡᱮ",
        "listusers": "ᱵᱮᱵᱦᱟᱨᱤᱡ ᱛᱟᱹᱞᱠᱟᱹ",
        "listusers-creationsort": "ᱛᱮᱭᱟᱨᱟᱠᱟᱱ ᱢᱟᱹᱦᱤᱛ ᱞᱮᱠᱟᱛᱮ ᱯᱟᱱᱛᱮ",
        "usercreated": "{{GENDER:$3|ᱵᱮᱱᱟᱣᱠᱟᱱ}} $1 ᱢᱟᱹᱦᱤᱛᱨᱮ $2 ᱚᱠᱛᱚᱨᱮ",
        "listusers-blocked": "(ᱮᱥᱮᱫ ᱜᱮᱭᱟ)",
        "listgrouprights-group": "ᱜᱟᱶᱛᱟ",
        "listgrouprights-rights": "ᱟᱹᱭᱫᱟᱹᱨᱤᱠᱚ",
-       "listgrouprights-helppage": "ᱜᱚᱲᱚᱸ:ᱜᱟᱫᱮᱞ ᱟᱹᱭᱫᱟᱹᱨ",
+       "listgrouprights-helppage": "Help:ᱜᱟᱫᱮᱞ ᱟᱹᱭᱫᱟᱹᱨ",
        "listgrouprights-members": "(ᱥᱚᱦᱮᱫᱠᱩᱣᱟᱜ ᱛᱟᱹᱞᱠᱟᱹ)",
        "listgrouprights-addgroup-all": "ᱡᱚᱛᱚ ᱜᱟᱶᱛᱟᱠᱩ ᱥᱮᱞᱮᱫ ᱠᱩ ᱢᱮ",
        "listgrouprights-removegroup-all": "ᱡᱚᱛᱚ ᱜᱟᱶᱛᱟᱠᱩ ᱚᱪᱚᱜ ᱠᱩ ᱢᱮ",
        "undeletelink": "ᱧᱮᱞ/ᱫᱚᱦᱚ ᱨᱩᱣᱟᱹᱲ",
        "undeleteviewlink": "ᱧᱮᱞ",
        "undelete-search-submit": "ᱥᱮᱸᱫᱽᱨᱟ",
+       "undelete-show-file-submit": "ᱦᱮᱸ",
        "namespace": "ᱧᱤᱛᱩᱢ ᱡᱟᱜᱟ",
        "invert": "ᱥᱮᱪ ᱵᱟᱪᱷᱟᱣ",
        "tooltip-invert": "ᱱᱚᱶᱟ ᱵᱟᱠᱥᱟ ᱴᱤᱠ ᱢᱮ ᱥᱟᱦᱴᱟ ᱠᱷᱚᱱ ᱵᱚᱫᱚᱞᱟᱜᱠᱚ ᱫᱟᱱᱟᱝ ᱞᱟᱹᱜᱤᱫ  ᱵᱟᱛᱷᱚᱱ ᱨᱟᱠᱷᱟ ᱧᱩᱛᱩᱢ ᱥᱟᱶᱛᱮ (ᱟᱨ ᱡᱚᱯᱚᱲᱟᱣᱟᱱ ᱨᱟᱠᱷᱟ ᱧᱩᱛᱩᱢ ᱡᱩᱫᱤ ᱴᱤᱠ ᱟᱠᱟᱱᱟ)",
        "allmessagesname": "ᱧᱩᱛᱩᱢ",
        "allmessagesdefault": "Bań bhul mesag ol",
        "allmessages-filter-all": "ᱡᱚᱛᱚ",
+       "allmessages-language": "ᱯᱟᱹᱨᱥᱤ:",
        "allmessages-filter-submit": "ᱪᱟᱞᱟᱜ ᱢᱮ",
+       "allmessages-filter-translate": "ᱛᱚᱨᱡᱚᱢᱟ",
        "thumbnail-more": "ᱞᱟᱹᱴᱩᱭ ᱢᱮ",
        "thumbnail_error": "Benawakan unuduḱ kạṭuṕ do baṅ ṭhika: $1",
        "import-upload-filename": "ᱨᱮᱫᱧᱩᱛᱩᱢᱺ",
        "pageinfo-toolboxlink": "ᱥᱦᱟᱴᱟ ᱨᱮᱭᱟᱜ ᱠᱷᱚᱵᱚᱨ",
        "pageinfo-contentpage": "ᱩᱱᱩᱫᱩᱜ ᱥᱟᱦᱴᱟ ᱞᱮᱠᱟᱛᱮ ᱞᱮᱠᱷᱟ ᱦᱟᱠᱟᱱᱟ",
        "pageinfo-contentpage-yes": "ᱦᱮᱸ",
+       "pageinfo-protect-cascading-yes": "ᱦᱮᱸ",
        "patrol-log-page": "ᱛᱩᱱᱠᱷᱤᱭᱤᱡᱟᱜ ᱞᱚᱜᱽ",
        "previousdiff": "← ᱢᱟᱨᱮᱱᱟᱜ ᱥᱟᱯᱲᱟᱣ",
        "nextdiff": "ᱱᱟᱣᱟᱱᱟᱜ ᱥᱟᱯᱲᱟᱣ →",
        "exif-subsectime": "ᱢᱟᱹᱦᱤᱛ ᱚᱠᱛᱚ ᱴᱤᱯᱤᱡ",
        "exif-exposuretime-format": "$1 ᱴᱤᱯᱤᱡ ($2)",
        "exif-gpsdatestamp": "GPS ᱢᱟᱹᱦᱤᱛ",
+       "exif-languagecode": "ᱯᱟᱹᱨᱥᱤ",
        "exif-unknowndate": "ᱚᱪᱤᱱᱦᱟᱹᱣ ᱢᱟᱹᱦᱤᱛ",
        "exif-orientation-1": "ᱥᱟᱫᱷᱟᱨᱚᱱ",
+       "exif-contrast-0": "ᱱᱚᱨᱢᱟᱞ",
+       "exif-saturation-0": "ᱱᱚᱨᱢᱟᱞ",
+       "exif-sharpness-0": "ᱱᱚᱨᱢᱟᱞ",
        "exif-dc-date": "ᱢᱟᱹᱦᱤᱛ",
+       "exif-urgency-normal": "ᱱᱚᱨᱢᱟᱞ ($1)",
        "namespacesall": "ᱡᱚᱛᱚ",
        "monthsall": "ᱡᱚᱛᱚ",
        "quotation-marks": "\"$1\"",
        "tags-hitcount": "$1 {{PLURAL:$1|ᱟᱹᱨᱩ|ᱟᱹᱨᱩᱠᱚ}}",
        "compare-page1": "ᱥᱟᱦᱴᱟ ᱑",
        "compare-rev1": "ᱧᱮᱞᱟᱹᱨᱩ ᱑",
+       "htmlform-no": "ᱵᱟᱝ",
+       "htmlform-yes": "ᱦᱮᱸ",
        "logentry-delete-delete": "$3 ᱥᱟᱦᱴᱟ $1 {{GENDER:$2|ᱜᱮᱫ ᱠᱮᱜ-ᱟᱭ}}",
        "logentry-delete-restore": "$1 {{GENDER:$2|ᱨᱟᱠᱷᱟ ᱫᱚᱲᱦᱟ}} ᱠᱮᱜ-ᱟ ᱥᱟᱦᱴᱟ $3 ($4)",
        "logentry-delete-revision": "$1 {{GENDER:$2|ᱵᱚᱫᱚᱞᱠᱮᱜ-ᱟᱭ}} ᱧᱮᱞᱚᱜᱟᱜ {{PLURAL:$5|ᱫᱚᱦᱲᱟᱭᱮᱱᱟᱜ|$5 ᱫᱚᱦᱲᱟᱭᱮᱱᱟᱜ ᱠᱚ}} $3: $4 ᱥᱟᱦᱴᱟ ᱪᱮᱛᱟᱱᱨᱮ",
        "logentry-upload-overwrite": "$1 {{GENDER:$2|ᱞᱟᱫᱮᱭᱮᱱᱟ}} ᱢᱤᱫ ᱱᱟᱶᱟ ᱵᱷᱟᱨᱥᱚᱱ $3 ᱨᱮᱱᱟᱜ",
        "searchsuggest-search": "ᱥᱮᱸᱫᱽᱨᱟ {{SITENAME}}",
        "duration-days": "$1 {{PLURAL:$1|ᱢᱟᱦᱟᱸ|ᱢᱟᱸᱦᱟᱸ}}",
+       "pagelanguage": "ᱥᱟᱦᱴᱟ ᱯᱟᱹᱨᱥᱤ ᱵᱚᱫᱚᱞ",
+       "pagelang-language": "ᱯᱟᱹᱨᱥᱤ",
+       "right-pagelang": "ᱥᱟᱦᱴᱟ ᱯᱟᱹᱨᱥᱤ ᱵᱚᱫᱚᱞ",
        "mw-widgets-dateinput-no-date": "ᱢᱟᱹᱦᱤᱛ ᱵᱟᱝ ᱵᱟᱪᱷᱚᱱ ᱟᱠᱟᱱᱟ",
        "mw-widgets-mediasearch-input-placeholder": "ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ ᱢᱮᱰᱤᱭᱟ",
        "date-range-from": "ᱢᱟᱹᱦᱤᱛ ᱠᱷᱚᱱ:",
index 6f3fefc..4e2d7aa 100644 (file)
        "redirectedfrom": "(Reindiritzadu dae $1)",
        "redirectpagesub": "Pàgina de reindiritzamentu",
        "redirectto": "Reindiritzat a:",
-       "lastmodifiedat": "Ùrtimu càmbiu su $1, a is $2.",
+       "lastmodifiedat": "Ùrtima modìfica su $1, a is $2.",
        "viewcount": "Custu artìculu l'ant lèghidu {{PLURAL:$1|borta|$1 bortas}}.",
        "protectedpage": "Pàgina amparada",
        "jumpto": "Bae a:",
        "linkstoimage-redirect": "$1 (reindiritzamentu file) $2",
        "sharedupload": "Custu file benit dae $1 e podet èssere impreadu in àteros progetos.",
        "sharedupload-desc-here": "Custu documentu benit dae $1 e podet èssere impreadu in àteros progetos.\nA sighire est ammustrada sa descritzione in sa sua [$2 pàgina de descritzione de su documentu].",
+       "filepage-nofile": "Non b'at perunu documentu cun custu nùmene.",
        "uploadnewversion-linktext": "Carriga una versione noa de custu file",
        "shared-repo-from": "dae $1",
        "filerevert-comment": "Motivu:",
        "booksources": "Fontes libràrias",
        "booksources-search-legend": "Chirca fontes libràrias",
        "booksources-isbn": "ISBN:",
+       "booksources-search": "Chirca",
        "specialloguserlabel": "Atzione fata dae:",
        "speciallogtitlelabel": "Atzione fata subra:",
        "log": "Registros",
        "watchlist-options": "Optziones subra sa lista de pàginas annotadas",
        "watching": "Giunghende a sa watchlist...",
        "unwatching": "Boghende dae sa watchlist...",
+       "enotif_reset": "Marca totu sas pàginas bisitadas",
        "enotif_impersonal_salutation": "Impitadore de {{SITENAME}}",
        "enotif_anon_editor": "impitadore anònimu $1",
        "created": "creada",
        "contributions": "Cuntributos {{GENDER:$1|utente}}",
        "contributions-title": "Contributziones de $1",
        "mycontris": "Contributos mios",
+       "anoncontribs": "Contributziones",
        "contribsub2": "Pro {{GENDER:$3|$1}} ($2)",
        "nocontribs": "Nessuna modifica trovata conformemente a questi criteri.",
        "uctop": "(atuale)",
        "importstart": "Importande is pàginas...",
        "import-revision-count": "$1 {{PLURAL:$1|revisione|revisiones}}",
        "importlogpage": "Importatziones",
-       "tooltip-pt-userpage": "Sa pàgina utente tua",
+       "tooltip-pt-userpage": "Sa pàgina impitadore {{GENDER:|tua}}",
        "tooltip-pt-mytalk": "Sa pàgina de is cuntierras tuas",
-       "tooltip-pt-preferences": "Is preferèntzias chi podes seberare",
+       "tooltip-pt-preferences": "Is preferèntzias {{GENDER:|tuas}}",
        "tooltip-pt-watchlist": "Lista de is pàginas annotadas dae tue pro is mudàntzias",
        "tooltip-pt-mycontris": "Sa lista de is contributos mios",
        "tooltip-pt-login": "Sa registratzione est cussigiada; mancari chi non siat obligatoria",
        "tooltip-feed-rss": "RSS feed pro custa pàgina",
        "tooltip-feed-atom": "Atom feed pro custa pàgina",
        "tooltip-t-contributions": "Càstia sa lista de is cuntributos de custu utente",
-       "tooltip-t-emailuser": "Ispedi una email a custu impitadore",
+       "tooltip-t-emailuser": "Imbia una e-lìtera a {{GENDER:$1|custu impreadore}}",
        "tooltip-t-upload": "Càrriga documentu multimediale",
        "tooltip-t-specialpages": "Lista de is pàginas ispetziales",
        "tooltip-t-print": "Versione de custa pàgina pro s'imprenta",
        "tooltip-t-permalink": "Ligàmene permanente a custa versione de sa pàgina",
        "tooltip-ca-nstab-main": "Càstia su cuntenutu de sa pàgina",
        "tooltip-ca-nstab-user": "Càstia sa pàgina utente",
-       "tooltip-ca-nstab-special": "Custa est una pàgina ispetziale, non si podet modificare",
+       "tooltip-ca-nstab-special": "Custa est una pàgina ispetziale, non podet èssere modificada",
        "tooltip-ca-nstab-project": "Càstia sa pàgina de servìtziu",
        "tooltip-ca-nstab-image": "Càstia sa pàgina de su file",
        "tooltip-ca-nstab-template": "Càstia su template",
        "file-nohires": "Non si tenent risolutziones prus artas.",
        "svg-long-desc": "file in formadu SVG, mannesa nominale $1 × $2 pixel, mannesa de su file: $3",
        "show-big-image": "Versione a risolutzione arta",
+       "show-big-image-size": "$1 × $2 pixels",
        "imagelisttext": "Innoe sighendi du est una lista de '''$1''' {{PLURAL:$1|file|files}} ordinada $2.",
        "newimages-legend": "Filtru",
        "ilsubmit": "Chirca",
        "metadata-expand": "Ammustra particulares",
        "metadata-collapse": "Cua particulares",
        "metadata-fields": "Is campos de is metadatos de imàgine listados in custu messàgiu ant a èssere ammustrados in sa pàgina de s'immàgine candu sa tabella de is metadatos est presentada in forma breve. Pro impostatzione predefinida, is àteros campos ant a èssere cuaos.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
+       "exif-orientation": "Orientamentu",
        "exif-artist": "Autore",
        "exif-exposuretime-format": "$1 s ($2)",
        "exif-fnumber-format": "f/$1",
        "version-software-version": "Versione",
        "version-entrypoints-header-url": "URL",
        "redirect-submit": "Bae",
+       "redirect-value": "Valore:",
        "redirect-user": "ID impitadore",
        "redirect-page": "ID pàgina",
+       "redirect-revision": "Versione de sa pàgina",
+       "redirect-file": "Nùmene documentu",
        "fileduplicatesearch-submit": "Chirca",
        "specialpages": "Pàginas ispetziales",
        "specialpages-note-top": "Legenda",
        "feedback-close": "Fatu",
        "feedback-message": "Messàgiu:",
        "feedback-subject": "Ogetu:",
-       "searchsuggest-search": "Chirca",
+       "searchsuggest-search": "Chirca in intro de {{SITENAME}}",
        "expand_templates_ok": "OK",
        "expand_templates_preview": "Antiprima",
        "pagelang-name": "Pàgina",
index 5b8dc62..0a17742 100644 (file)
        "copyrightwarning": "ياد رکندا ته {{SITENAME}} لاءِ سموريون ڀاڱيداريون $2 تحت پڌريون ڪجن ٿيون (تفصيلن لاءِ $1 ڏسندا). اوهان جي تحرير کي {{SITENAME}} جي قائدن تحت ترميمي سگهجي ٿو. جيڪڏهن اوهان نه ٿا چاهيو ته اوهان جي لکڻين کي بي رحميءَ سان ترميميو وڃي يا ورهائي عام ڪيو وڃي ته پوءِ پنهنجي لکڻي هتي جمع نه ڪرايو. پنهنجو مواد هتي جمع ڪرڻ جو مطلب هوندو ته توهان کي جمع ڪرايل مواد جي مفت فراهمي ۽ کُليل تبديليءَ تي ڪو به اعتراز ناهي.<br />\nتوهان اهڙي پڪ ڏيڻ جا پابند پڻ آهيو ته توهان جو جمع ڪرايل مواد توهان جو پنهنجو لکيل آهي يا وري توهان ڪنهن مفت وسيلي تان ڪاپي ڪيو آهي.\n'''تحفظيل حق ۽ واسطا رکندڙ مواد واسطيدار مالڪ کان اڳواٽ اجازت وٺڻ کان سواءِ هتي جمع نه ڪريو.'''",
        "copyrightwarning2": "ياد رکندا تہ {{SITENAME}} لاءِ سموريون ڀاڱيدارين کي ٻيا ڀاڱيدار سنواري، بدلائي، يا ڊاهي سگھن ٿا. جيڪڏهن اوهان نہ ٿا چاهيو تہ اوهان جي لکڻين کي بي رحميءَ سان ترميميو وڃي يا ورهائي عام ڪيو وڃي تہ پوءِ پنهنجي لکڻي هتي جمع نہ ڪرايو.</br>\nتوهان اهڙي پڪ ڏيڻ جا پابند پڻ آهيو تہ توهان جو جمع ڪرايل مواد توهان جو پنهنجو لکيل آهي يا وري توهان ڪنهن اهڙي ئي مفت عوامي وسيلي تان ڪاپي ڪيو آهي. (تفصيلن لاءِ $1 ڏسندا).\n\n<strong>تحفظيل حق ۽ واسطا رکندڙ مواد واسطيدار مالڪ کان اڳواٽ اجازت وٺڻ بنان هتي جمع نہ ڪريو.</strong>",
        "protectedpagewarning": "<strong>چتاءُ: هيءَ صفحو اهڙيءَ ريت تحفظيو ويو آهي جو فقط منتظمين ئي ان کي سنواري سگھن ٿا. </strong>\nتازه ترين لاگ حوالي طور پيش ڪجي ٿو:",
-       "semiprotectedpagewarning": "<strong>نوٽ:</strong> هيءَ صفحو اهڙيءَ ريت تحفظيو ويو آهي جو فقط کاتيدار واپرائيندڙ ئي ان کي سنواري سگھن ٿا.\nتازه ترين لاگ حوالي طور پيش ڪجي ٿو:",
+       "semiprotectedpagewarning": "<strong>نوٽ:</strong> هيءَ صفحو اهڙيءَ ريت تحفظيو ويو آهي جو فقط خودڪار نموني پڪ ڪيل واپرائيندڙ ئي ان کي سنواري سگھن ٿا.\nتازه ترين لاگ حوالي طور پيش ڪجي ٿو:",
        "templatesused": "هن صفحي تي استعمال ٿيندڙ {{PLURAL:$1|سانچو|سانچا}}:",
        "templatesusedpreview": "هن پيش نگاھ ۾ استعمال ٿيل {{PLURAL:$1|سانچو|سانچا}}:",
        "templatesusedsection": "هن سيڪشن ۾ استعمال ٿيل {{PLURAL:$1|سانچو|سانچا}}:",
        "recentchangesdays": "تازين تبديلين ۾ ڏيکارڻ جي لاءِ ڏينهن:",
        "recentchangesdays-max": "وڌ ۾ وڌ $1 {{PLURAL:$1|ڏينهن}}",
        "recentchangescount": "عدم پيروي جي صورت ۾ ڏيکارڻ جي لاءِ ترميمون:",
-       "prefs-help-recentchangescount": "ان ۾ تازيون تبديليون، صفحن جي سوانح، ۽ لاگ شامل آهن.",
+       "prefs-help-recentchangescount": "وڌ ۾ وڌ انگ: 1000",
        "savedprefs": "توھان جون ترجيحون سانڍجي چڪيون آھن.",
        "savedrights": "{{GENDER:$1|$1}} جا واپرائيندڙ گروھ سانڍجي چڪا آھن.",
        "timezonelegend": "اوقاتي زون:",
diff --git a/languages/i18n/shy-latn.json b/languages/i18n/shy-latn.json
new file mode 100644 (file)
index 0000000..170c9b8
--- /dev/null
@@ -0,0 +1,1110 @@
+{
+       "@metadata": {
+               "authors": [
+                       "Vikoula5"
+               ]
+       },
+       "tog-underline": "Aderrer n iseɣwan:",
+       "tog-hideminor": "Ffer ibeddilen ifessasen deg yibeddilen imaynuten",
+       "tog-hidepatrolled": "Ffer ibeddilen yettwaɣran deg yibeddilen imaynuten",
+       "tog-newpageshidepatrolled": "Ffer isebtar yettwaɣran deg tebdart n isebtar imaynten",
+       "tog-hidecategorization": "Ffer taggayin n isebtar",
+       "tog-extendwatchlist": "Ssemɣer umuɣ n uɛessi iwakken ad muqleɣ akk n wayen zemreɣ ad beddleɣ",
+       "tog-usenewrc": "Ssegrew ibeddlen s usebtar deg ibeddilen imaynuten d umuɣ n uḍfar",
+       "tog-numberheadings": "Izwal ɣur-sen imḍanen mebla ma serseɣ-iten",
+       "tog-showtoolbar": "Ssken tafeggagt n ifecka n ubeddel",
+       "tog-editondblclick": "Beddel isebtar mi wekkiɣ snat n tikwal",
+       "tog-editsectiononrightclick": "Ssermed abeddel n tigezmi s ukliki ayeffus ɣef izwal",
+       "tog-watchcreations": "Rnu isebtar i xelqeɣ deg wumuɣ n uɛessi inu",
+       "tog-watchdefault": "Rnu isebtar i ttbeddileɣ deg wumuɣ n uɛessi inu",
+       "tog-watchmoves": "Rnu isebtar i smimḍeɣ deg wumuɣ n uɛessi inu",
+       "tog-watchdeletion": "Rnu isebtar i mḥiɣ deg wumuɣ n uɛessi inu",
+       "tog-watchuploads": "Rnu ifuyla imaynuten i suliɣ ar tebdart-iw n uqreɛ",
+       "tog-watchrollback": "Rnu ar tebdart-iw n uḍfaṛ n isebtar anida hwiɣ albaɛd",
+       "tog-minordefault": "Rcem akk ibeddlen am ibeddlen ifessasen d ameslugen",
+       "tog-previewontop": "Ssken pre-timeẓriwt uqbel tankult ubeddel",
+       "tog-previewonfirst": "Ssken pre-timeẓriwt akk d ubeddel amezwaru",
+       "tog-enotifwatchlistpages": "Azen-iyi-d e-mail m'ara yettubeddel asebter i ttɛassaɣ",
+       "tog-enotifusertalkpages": "Azen-iyi-d e-mail asmi sɛiɣ izen amaynut",
+       "tog-enotifminoredits": "Azen-iyi-d e-mail ma llan ibeddlen ifessasen",
+       "tog-enotifrevealaddr": "Ssken e-mail inu asmi yettwazen email n talɣut",
+       "tog-shownumberswatching": "Ssken geddac yellan n yimseqdacen iɛessasen",
+       "tog-oldsig": "Azmul-ik yellan :",
+       "tog-fancysig": "Eǧǧ azmul am yettili (war azday awurman)",
+       "tog-uselivepreview": "Sken tiskanin s war asmiren n usebter",
+       "tog-forceeditsummary": "Ini-iyi-d mi sskecmeɣ agzul amecluc",
+       "tog-watchlisthideown": "Ffer ibeddlen inu seg wumuɣ n uɛessi inu",
+       "tog-watchlisthidebots": "Ffer ibeddlen n iboṭiyen seg wumuɣ n uɛessi inu",
+       "tog-watchlisthideminor": "Ffer ibeddlen ifessasen seg wumuɣ n uɛessi inu",
+       "tog-watchlisthideliu": "Ffer ibeddlen n iseqdacen yelan deg umuɣ n tiḍefri",
+       "tog-watchlistreloadautomatically": "Ales asali s wudem awurman n tebdart n uḍfaṛ ticki iɣewwaṛen n usizdeg ttwabeddlen (Ilaq JavaScript)",
+       "tog-watchlistunwatchlinks": "Rnu iseɣwan srid n uḍfaṛ/aseḥbes ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) n uḍfaṛ i yinekcam n tebdart n uḍfaṛ(Ilaq JavaScript i usefrek n tmahilt-agi)",
+       "tog-watchlisthideanons": "Ffer ibeddlen n iseqdacen udrigen deg umuɣ n tiḍefri",
+       "tog-watchlisthidepatrolled": "Ffer ibeddlen iɛessan deg umuɣ n tiḍefri",
+       "tog-watchlisthidecategorization": "Ffer taggayin n isebtar",
+       "tog-ccmeonemails": "Azen-iyi-d email n wayen uzneɣ i imseqdacen wiyaḍ",
+       "tog-diffonly": "Ur temliḍ-iyi-d ara ayen yellan seddaw imgerraden",
+       "tog-showhiddencats": "Beqqeḍ taggayin yeffren",
+       "tog-norollbackdiff": "Ur skan  ara amgired seld aḥway",
+       "tog-useeditwarning": "Σeggen iyid mi ara fγaγ seg usebter mebla ma skeslaγ ibeddilen.",
+       "tog-prefershttps": "Seqdac yal tikelt tuqqna taɣelsant akken ad teqqneḍ",
+       "underline-always": "Yal tikelt",
+       "underline-never": "Werǧin",
+       "underline-default": "Azal s lexṣas n iminig neɣ n usentel",
+       "editfont-style": "Aɣanib n tasefsit n taɣzut ubeqqeḍ",
+       "editfont-monospace": "Tasefsit s lqedd usbiḍ",
+       "editfont-sansserif": "Tasefsit \"Sans-serif\"",
+       "editfont-serif": "Tasefsit \"Serif\"",
+       "sunday": "Acer",
+       "monday": "Arim",
+       "tuesday": "Aram",
+       "wednesday": "Ahad",
+       "thursday": "Amhad",
+       "friday": "Sem",
+       "saturday": "Sed",
+       "sun": "Ace.",
+       "mon": "Ari.",
+       "tue": "Ara.",
+       "wed": "Aha.",
+       "thu": "Amh.",
+       "fri": "Sem",
+       "sat": "Sed",
+       "january": "Yannayer",
+       "february": "Furar",
+       "march": "Meɣres",
+       "april": "Brir",
+       "may_long": "Mayu",
+       "june": "Yunyu",
+       "july": "Yulyez",
+       "august": "Ɣuct",
+       "september": "Ctember",
+       "october": "Kuber",
+       "november": "Wember",
+       "december": "Jember",
+       "january-gen": "Yannayer",
+       "february-gen": "Furar",
+       "march-gen": "Meɣres",
+       "april-gen": "Brir",
+       "may-gen": "Mayu",
+       "june-gen": "Yunyu",
+       "july-gen": "Yulyez",
+       "august-gen": "Ɣuct",
+       "september-gen": "Ctember",
+       "october-gen": "Kuber",
+       "november-gen": "Wember",
+       "december-gen": "Jember",
+       "jan": "Yan",
+       "feb": "Fur",
+       "mar": "Meɣ",
+       "apr": "Bri",
+       "may": "May",
+       "jun": "Yun",
+       "jul": "Yul",
+       "aug": "Ɣuc",
+       "sep": "Cte",
+       "oct": "Kub",
+       "nov": "Wem",
+       "dec": "Jem",
+       "january-date": "Yannayer $1",
+       "february-date": "Furar $1",
+       "march-date": "Meɣres $1",
+       "april-date": "Brir $1",
+       "may-date": "Mayu $1",
+       "june-date": "Yunyu $1",
+       "july-date": "Yulyez $1",
+       "august-date": "Ɣuct $1",
+       "september-date": "Ctember $1",
+       "october-date": "Kuber $1",
+       "november-date": "Wember $1",
+       "december-date": "Jember $1",
+       "period-am": "FT",
+       "period-pm": "MD",
+       "pagecategories": "{{PLURAL:$1|Taggayt|Taggayin}}",
+       "category_header": "Isebtar deg taggayt \"$1\"",
+       "subcategories": "Adutaggayin",
+       "category-media-header": "Media deg taggayt \"$1\"",
+       "category-empty": "<em>Taggayt agi ur tesɛa asebtar, adu-taggayt neɣ afaylu agetmedia.</em>",
+       "hidden-categories": "{{PLURAL:$1|Taggayt yeffren|Taggayin yeffren}}",
+       "hidden-category-category": "Taggayin yeffren",
+       "category-subcat-count": "Taggayt-agi {{PLURAL:$2|0=ur tegbir ula d yiwet n taggayt tasnawt|1=tegber kan taggayt tasnawant ddaw-a|tegber $2 n taggayin tisnawanin, gar-asent {{PLURAL:$1|0=ula d yiwet|1=tin|tigad $1}} ddaw-a}}.",
+       "category-subcat-count-limited": "Taggayt-agi tegber {{PLURAL:$1|n taggayt tasnawant|$1 n taggayin tisnawanin}} ddaw-a.",
+       "category-article-count": "Taggayt-agi{{PLURAL:$2|0=ur tegbur ula d yiwen n usebtert|1=tegber kan yiwen n usebterddaw-a|tegber $2 n isebtar, gar-asen {{PLURAL:$1|0=ula d yiwen|1=tin| $1}} n ddaw-a}}.",
+       "category-article-count-limited": "{{PLURAL:$1|Asebter agi yella|$1 isebtar agi llan}} deg taggayt agi.",
+       "category-file-count": "Taggayt agi tesɛa {{PLURAL:$2|afaylu agi|$2 ifuyla, ɣef ayed {{PLURAL:$1|t-agi|t-igi $1}} ddaw-agi}}.",
+       "category-file-count-limited": "{{PLURAL:$1|Afaylu agi yella|$1 ifuyla agi llan}} deg taggayt agi.",
+       "listingcontinuesabbrev": "asartu",
+       "index-category": "Isebtar s umatar",
+       "noindex-category": "Asebter-agi ur ɣur-s ara amatar",
+       "broken-file-category": "Isebtar s iseɣwan n ifuyla iṛzan",
+       "about": "Awal ɣef...",
+       "article": "Ayen yella deg usebter",
+       "newwindow": "(ad d-yeldi deg usfaylu amaynut)",
+       "cancel": "Eǧǧ-it am yella",
+       "moredotdotdot": "Ugar...",
+       "morenotlisted": "Tabdart-agi ur temmid ara",
+       "mypage": "Asebter",
+       "mytalk": "Asqerdec",
+       "anontalk": "Asqerdec",
+       "navigation": "Assilel",
+       "and": "&#32;akked",
+       "faq": "Isteqsiyen FAQ",
+       "actions": "Tigawtin",
+       "namespaces": "Talluntin n isemawen",
+       "variants": "Tineḍwa",
+       "navigation-heading": "Umuɣ n tunigin",
+       "errorpagetitle": "Agul",
+       "returnto": "Uɣal ar $1.",
+       "tagline": "Seg {{SITENAME}}",
+       "help": "Tallalt",
+       "search": "Iruzzi",
+       "search-ignored-headings": "#<!-- eǧǧ izirigen-agi akken llan --><pre>\n# Izwal n tgezmiyin ad ttwazeglen deg unadi\n# Ibeddilen yettwagen dagi ad ddun ticki asebter s uzwel yettwarna ar umatar.\n# Tzemreḍ ad ḥettmeḍ tulsa n tmerna ar umatar n usebter s usnifel ilem\n# Taseddast d tagi :\n#   * Yal iziirig ibeddun s \"#\" d awennit\n#   * Yal izirig yeččuren d azwel ara tzegleḍ, ula d taṛuzi n usekkil\nTimsisɣal\nIseɣwan izɣarayen\nWali daɣen\n #</pre><!-- eǧǧ izirig-agi akken yella -->",
+       "searchbutton": "Iruzzi",
+       "go": "Ẓer",
+       "searcharticle": "Ẓer",
+       "history": "Amezruy n usebter",
+       "history_short": "Amazray",
+       "history_small": "amazray",
+       "updatedmarker": "yettubeddel segmi tarzeft taneggarut inu",
+       "printableversion": "Lqem n usiggez",
+       "permalink": "Azday ur yettbeddil ara",
+       "print": "Siggez",
+       "view": "Ɣeṛ",
+       "view-foreign": "Sken di $1",
+       "edit": "Glef",
+       "edit-local": "Rnu aglam adigan",
+       "create": "Snulfu",
+       "create-local": "Rnu aglam n adigan",
+       "delete": "Mḥu",
+       "undelete_short": "Fakk amḥay n {{PLURAL:$1|yiwen ubeddel|$1 yibeddlen}}",
+       "viewdeleted_short": "Ẓeṛ {{PLURAL:$1|yiwen abeddel yettumḥan|$1 Ibeddlen yettumḥan}}",
+       "protect": "Ḥrez",
+       "protect_change": "beddel tiḥḥerzi",
+       "unprotect": "Beddel amesten",
+       "newpage": "Asebter amaynut",
+       "talkpagelinktext": "Mmeslay",
+       "specialpage": "Asebter uslig",
+       "personaltools": "Dduzan inu",
+       "talk": "Asqerdec",
+       "views": "Tuẓrin",
+       "toolbox": "Ifecka",
+       "tool-link-userrights": "Snifel igrawen n {{GENDER:$1|useqdac}}",
+       "tool-link-userrights-readonly": "Sken igrawen n {{GENDER:$1|useqdac}}",
+       "tool-link-emailuser": "Azen emayl i {{GENDER:$1|useqdac}}",
+       "imagepage": "Ẓer asebter n tugna",
+       "mediawikipage": "Ẓer asebter n izen",
+       "templatepage": "Ẓer asebter n talɣa",
+       "viewhelppage": "Ẓer asebter n tallalt",
+       "categorypage": "Ẓer asebter n taggayin",
+       "viewtalkpage": "Wali asqerdec",
+       "otherlanguages": "S tutlayin tiyaḍ",
+       "redirectedfrom": "(Yettusmimeḍ seg $1)",
+       "redirectpagesub": "Asebter usemmimeḍ",
+       "redirectto": "Welleh ar:",
+       "lastmodifiedat": "Asebter-agi ibeddel i tikelt taneggarut di $2, $1.",
+       "viewcount": "Asebter-agi yettwakcem {{PLURAL:$1|yiwet tikelt|$1 tikwal}}.",
+       "protectedpage": "Asebter yettwaḥerzen",
+       "jumpto": "Neggez ar:",
+       "jumptonavigation": "ẓer isebtar",
+       "jumptosearch": "iruzzi",
+       "view-pool-error": "Suref-aɣ, iqeddacen iwziren tura.\nAṭas iseqdacen tnadin ad ẓṛen asebter agi.\nIlaq ad arǧuḍ imir uqbel ad εreḍeḍ tikkelt nniḍen .\n\n$1",
+       "generic-pool-error": "Suref-aɣ, iqeddacen ur stufan ara akka tura.\nDdeqs n iseqdacen ttnadin ad ẓṛen taɣbalut-agi.\nMa ulac aɣilif, rǧu cwiṭ send ad tεreḍeḍ ad tkecmeḍ tikkelt-nniḍen.",
+       "pool-timeout": "Amenḍar iɛedda deg taganit n uzekṛun",
+       "pool-queuefull": "Adras n umahil yečuṛ",
+       "pool-errorunknown": "Tuccḍa tarussint",
+       "pool-servererror": "Amẓlu n uḥerri ur iheegi ara ($1)",
+       "poolcounter-usage-error": "Tuccḍa  n useqdec: $1",
+       "aboutsite": "Ɣef {{SITENAME}}",
+       "aboutpage": "Project:Ɣef",
+       "copyright": "Agbur yella ddaw $1 ḥaca ma abdar anemgal.",
+       "copyrightpage": "{{ns:project}}:Copyrights",
+       "currentevents": "Isallen",
+       "currentevents-url": "Project:Isallen",
+       "disclaimers": "Ilɣa",
+       "disclaimerpage": "Project:Ilɣa imuta",
+       "edithelp": "Tallelt ɣef teẓrigt",
+       "helppage-top-gethelp": "Tallelt",
+       "mainpage": "Amzawru Tasfhit",
+       "mainpage-description": "Amzawru Tasfhit",
+       "policy-url": "Project:Ilugan",
+       "portal": "Awwur n timetti",
+       "portal-url": "Project:Awwur n timetti",
+       "privacy": "Tasertit n tbaḍnit",
+       "privacypage": "Project:Tasertit n tbaḍnit",
+       "badaccess": "Agul n turagt",
+       "badaccess-group0": "Ur tettalaseḍ ara ad texedmeḍ tigawt i tseqsiḍ.",
+       "badaccess-groups": "Tigawt id steqsiḍ t-uffar kan i iseqdacen n {{PLURAL:$2|ugraw|igrawen}} : $1.",
+       "versionrequired": "Yessefk ad tesɛiḍ tasiwelt $1 n MediaWiki",
+       "versionrequiredtext": "Yessefk ad tesɛiḍ tasiwelt $1 n MediaWiki iwakken ad tesseqdceḍ asebter-agi. Ẓer [[Special:Version|tasiwelt n usebter]].",
+       "ok": "Seɣbel",
+       "retrievedfrom": "Yettwaddem seg \"$1\"",
+       "youhavenewmessages": "{{PLURAL:$3|Ɣur-k}} $1 ($2).",
+       "youhavenewmessagesfromusers": "{{PLURAL:$4|Tesɛiḍ}}  $1 n {{PLURAL:$3|useqdac nniḍen|$3 iseqdacen nniḍen}} ( $2 ).",
+       "youhavenewmessagesmanyusers": "Tesɛiḍ $1 n aṭas n iseqdacen ($2).",
+       "newmessageslinkplural": "{{PLURAL:$1|izen amaynut|999=inzan imaynuten}}",
+       "newmessagesdifflinkplural": "{{PLURAL:$1|abeddel aneggaru|999=ibeddilen ineggura}}",
+       "youhavenewmessagesmulti": "Tesɛiḍ iznan imaynuten deg $1",
+       "editsection": "glef",
+       "editold": "glef",
+       "viewsourceold": "ẓeṛ aɣbalu",
+       "editlink": "glef",
+       "viewsourcelink": "ẓeṛ aɣbalu",
+       "editsectionhint": "Glef tagzemt: $1",
+       "toc": "Agbur",
+       "showtoc": "Ssken",
+       "hidetoc": "Ffer",
+       "collapsible-collapse": "Seggelmes",
+       "collapsible-expand": "Beqqeḍ",
+       "confirmable-confirm": "D tidett d {{GENDER:$1|kečč|kem}}?",
+       "confirmable-yes": "Ih",
+       "confirmable-no": "Uhu",
+       "thisisdeleted": "Ẓer neɣ err $1 am yella?",
+       "viewdeleted": "Ẓer $1?",
+       "restorelink": "{{PLURAL:$1|Yiwen abeddel yettumḥan|$1 Ibeddlen yettumḥan}}",
+       "feedlinks": "Asuddem:",
+       "feed-invalid": "Anaw n usuddem mačči ṣaḥiḥ.",
+       "feed-unavailable": "Isuddman RSS ur yestufan ara",
+       "site-rss-feed": "Asuddem RSS n $1",
+       "site-atom-feed": "Taneflit Atom n $1",
+       "page-rss-feed": "Asuddem RSS n \"$1\"",
+       "page-atom-feed": "Taneflit Atom n \"$1\"",
+       "red-link-title": "$1 (ulac asebter)",
+       "sort-descending": "Afran akaray",
+       "sort-ascending": "Afran aseffes",
+       "nstab-main": "Amagrad",
+       "nstab-user": "Asebter n wemseqdac",
+       "nstab-media": "Asebter n media",
+       "nstab-special": "Asebter uzzig",
+       "nstab-project": "Awal ɣef...",
+       "nstab-image": "Afaylu",
+       "nstab-mediawiki": "Izen",
+       "nstab-template": "Tamudemt",
+       "nstab-help": "Tallalt",
+       "nstab-category": "Taggayt",
+       "mainpage-nstab": "Asebter agejdan",
+       "nosuchaction": "Tigawt ulac-itt",
+       "nosuchactiontext": "Wiki ur teɛqil ara tigawt-nni n URL",
+       "nosuchspecialpage": "Asebter uslig am wagi ulac-it.",
+       "nospecialpagetext": "<strong>Tseqsiḍ ɣef usebter uslig ulac-it, tzemreḍ ad tafeḍ isebtar</strong>\nusligen n ṣṣeḥ deg [[Special:SpecialPages|wumuɣ n isebtar usligen]].",
+       "error": "Agul",
+       "databaseerror": "Agul n database",
+       "databaseerror-text": "Tuccḍa n tuttra deg taffa n isefka teḍra-d. Ahat yella afuqes deg useɣẓan.",
+       "databaseerror-textcl": "Tuccḍa n tuttra deg taffa n isefka teḍra-d",
+       "databaseerror-query": "Tuttra : $1",
+       "databaseerror-function": "Tuccḍa: $1",
+       "databaseerror-error": "Agul: $1",
+       "laggedslavemode": "<strong>Aɣtal:</strong> Ahat asebter ur yesɛi ara akk ibeddlen imaynuten.",
+       "readonly": "Database d tamsekkert",
+       "enterlockreason": "Ini ayɣer tsekkreḍ database, ini daɣen melmi ara ad ifukk asekker",
+       "readonlytext": "Taffa n isefka teskweṛ akka tura ɣef sebba n kra n inekcam d usnifel-nniḍen, ahat ɣef kra n uxeddim n uṣeggem, ticki ifuk ad tuɣal.\n\nAndbal n unagraw iṣekṛen taffa yenna-d acimi: $1",
+       "missing-article": "Taffa n isefka ur t-ufa ara aḍris n yiwen usebter ilaq at af, s-isem \"$1\"$2.\n\nUmata, wagi yeḍra mi neḍfeṛ azday ɣer yiwen diff aqbur naɣ ɣer amazray n usebter yemḥan.\n\nMa mačči d-tajṛut agi, ihi d-taniwit deg uhil.\nIlaq ad εeggenem yiwen [[Special:ListUsers/sysop|anedbal]] war ad ttum asefkem URL n uzday.",
+       "missingarticle-rev": "(uṭṭun n lqem#: $1)",
+       "missingarticle-diff": "(Diff: $1, $2)",
+       "readonly_lag": "Database d tamsekkert (weḥdes) axaṭer kra n serveur ɛeṭṭlen",
+       "internalerror": "Agul zdaxel",
+       "internalerror_info": "Anezri agensan : $1",
+       "filecopyerror": "Ur yezmir ara ad yexdem alsaru n ufaylu \"$1\" ar \"$2\".",
+       "filerenameerror": "Ur yezmir ara ad ibeddel isem ufaylu \"$1\" ar \"$2\".",
+       "filedeleteerror": "Ulamek an mḥu afaylu \"$1\".",
+       "directorycreateerror": "Ulamek an snulfu akaram \"$1\"",
+       "directoryreadonlyerror": "Akaram \"$1\" i tɣuri kan.",
+       "directorynotreadableerror": "Akaram \"$1\" ur yettwaɣray ara.",
+       "filenotfound": "Ur yezmir ara ad yaf afaylu \"$1\".",
+       "unexpected": "Agul: \"$1\"=\"$2\".",
+       "formerror": "Agul: ur yezmir ara ad yazen talɣa",
+       "badarticleerror": "Ur yezmir ara ad yexdem tigawt-agi deg usebter-agi.",
+       "cannotdelete": "Ulamek ad yemḥu asebter naɣ afaylu \"$1\".\nAhat amdan wayeḍ yemḥa-t.",
+       "cannotdelete-title": "Ulamek an kkes  asebter \"$1\"",
+       "delete-hook-aborted": "Tukkesa tesemmet s usiɣzef.\nUlac asefru ɣef wagi.",
+       "no-null-revision": "Ur nezmer ara ad n-snulfu tacaggart tilemnt tamaynut i usebtar \"$1\"",
+       "badtitle": "Azwel ur yelhi",
+       "badtitletext": "Asebter i testeqsiḍ fell-as mačči ṣaḥiḥ, d ilem, neɣ yella ugul deg wezday seg wikipedia s tutlayt tayeḍ neɣ deg wezday n wiki nniḍen. Ahat tesɛa asekkil ur yezmir ara ad yettuseqdac deg wezwel.",
+       "title-invalid-empty": "Azwel n usebter d-tessutreḍ d ilem neɣ yegber kan isem n tallunt n yismawen.",
+       "title-invalid-utf8": "Azwel n usebter d-tessutreḍ yegber tagzemt UTF-8 taruɣbilt",
+       "title-invalid-talk-namespace": "Azwel n usebter d-tessutreḍ yettwellih ar usebter n usqerdec ay izemren ulac-it",
+       "title-invalid-characters": "Azwel n usebter d-tessutreḍ yegber isekkilen iruɣbilen: \"$1\".",
+       "perfcached": "Talɣut deg ukessar seg lkac u waqila mačči d tasiwelt taneggarut. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.",
+       "perfcachedts": "Talɣut deg ukessar seg lkac, tasiwelt taneggarut n wass $1. A maximum of {{PLURAL:$4|one result is|$4 results are}} available in the cache.",
+       "querypage-no-updates": "Ibeddlen n usebter-agi ur ttbanen ara tura. Tilɣa ines qrib a d-banen.",
+       "viewsource": "Ẓer aɣbalu",
+       "viewsource-title": "Ẓeṛ aɣbalu n $1",
+       "actionthrottled": "Tigawt tesɛa talast",
+       "actionthrottledtext": "Akken ad nsiḥbiber mgal tuzna n ispamen, aseqdec n tigawt-agi tesɛa talast n umḍan n tikalt n wakud wezzilen kan ihi tɛeddaḍ talast.\nƐred tikelt-nniḍen di kra n tisdatin.",
+       "protectedpagetext": "Asebter-agi yetwaḥrez i uqareɛ n ubeddel neɣ tigawin nniḍen.",
+       "viewsourcetext": "Tzemreḍ ad twaliḍ u txedmeḍ alsaru n uɣbalu n usebter-agi:",
+       "viewyourtext": "Tzemṛeḍ ad ẓṛeḍ dɣa ad nɣeleḍ agbur n \"ibeddlen inek/inem\" deg usebter agi :",
+       "protectedinterface": "Asebter-agi d amsekker axaṭer yettuseqdac i weḍris n software.",
+       "editinginterface": "<strong>Ɣuṛ-k:</strong> Aqla-k tettbeddileḍ asebter yettuseqdacen i tmerna n uḍris n ugrudem n useɣẓan. \nIbeddilen ɣef usebter-agi ad ḥazen udem n ugrudem n useqdac i yiseqdacen-nniḍen n uwiki.",
+       "translateinterface": "Akken ad ternuḍ neɣ ad tbeddleḍ tisuqilin i yiwikiyen meṛṛa, seqdec [https://translatewiki.net/ translatewiki.net], asenfaṛ n usideg utlayan n MediaWiki.",
+       "cascadeprotected": "Asebter-agi yettwammesten mgal abeddel acku yedda deh {{PLURAL:$1|usebter d-iteddun yettwammestnen|isebtar-agi d-itedduun yettwammestnen}} s usefran \"ammesten s udabdar\" urmid:\n$2",
+       "namespaceprotected": "Ur tesɛiḍ ara turagt iwakken ad beddeleḍ isebtar n tallunt n isemawen <strong>$1</strong>.",
+       "customcssprotected": "Ur tesɛiḍ ara turagt iwakken ad beddeleḍ asebter agi n CSS, acku tesɛa iɣewwaren n yiwen useqdac nniḍen.",
+       "customjsprotected": "Ur tesɛiḍ ara turagt iwakken ad beddeleḍ asebter agi n Javascript, acku tesɛa iɣewwaren n yiwen useqdac nniḍen.",
+       "mycustomcssprotected": "Ur tesɛiḍ ara turagt i ubeddel n usebtar agi CSS.",
+       "mycustomjsonprotected": "Ur tesɛiḍ ara turagt i ubeddel n usebtar agi JSON.",
+       "mycustomjsprotected": "Ur tesɛiḍ ara turagt i ubeddel n usebtar agi JavaScript.",
+       "myprivateinfoprotected": "Ur tesɛiḍ ara turagt ad beddeleḍ tilɣa inek(em) tusligtin.",
+       "mypreferencesprotected": "Ur tesɛiḍ ara turagt ad beddeleḍ iɣewwaren inek(em).",
+       "ns-specialprotected": "Ur t-zemred ara ad beddeleḍ isebtar usligen",
+       "titleprotected": "Azwel agi yegdel deg usnulfu ɣef [[User:$1|$1]].\nTaɣẓint id yenna : <em>$2</em>",
+       "filereadonlyerror": "Ulamek ad nbeddel afaylu \"$1\" acku akaram n ifuyla \"$2\" yella deg uskar n tɣuri kan.\n\nAnedbal n unagraw i t-isekkweṛen, yefka-d asegzi-agi : \"$3\".",
+       "invalidtitle-knownnamespace": "Azwel ur i ɣbel ara s tallunt n isemawen « $2 » dɣa d-uglam « $3 »",
+       "invalidtitle-unknownnamespace": "Azwel ur i ɣbel ara s uṭṭun n tallunt n isemawen $1 dɣa d-uglam « $2 » warisem",
+       "exception-nologin": "Ur tekcimeḍ ara",
+       "exception-nologin-text": "Qqen akken ad tizmireḍ ad tkecmeḍ ar usebter-agineɣ tigawt-agi.",
+       "exception-nologin-text-manual": "$1 iwakken ad kecmeḍ ar asebtar neɣ tigawt agi.",
+       "virus-badscanner": "Yir tawila : anafraḍ n infafaden warisem : <em>$1</em>",
+       "virus-scanfailed": "Abrir n unadi (tangalt $1)",
+       "virus-unknownscanner": "amgelanfafad warisem :",
+       "logouttext": "<strong>Tura tesensereḍ.</strong>\n\nKra n isebtar zemren ad sskanen belli mazal-ik s yisem n wemseqdac inek armi temḥuḍ tazarkatut.",
+       "cannotlogoutnow-title": "Ur tezmireḍ ara ad teffɣeḍ tura",
+       "cannotlogoutnow-text": "Tuffɣa d tawezɣit ticki tseqdaceḍ $1.",
+       "welcomeuser": "Anṣuf, $1 !",
+       "welcomecreation-msg": "Amian inek(em) yesnulfad.\nTzemreḍ ad beddeleḍ {{SITENAME}} [[Special:Preferences|ismenyifen]] inek(em) ma tebɣiḍ",
+       "yourname": "Isem n useqdac:",
+       "userlogin-yourname": "Isem n useqdac",
+       "userlogin-yourname-ph": "Sekcem isem-ik(im) n useqdac",
+       "createacct-another-username-ph": "Sekcem isem n useqdac",
+       "yourpassword": "Awal n uɛeddi:",
+       "userlogin-yourpassword": "Awal n uɛeddi",
+       "userlogin-yourpassword-ph": "Sekcem awal-ik(im) n uɛeddi",
+       "createacct-yourpassword-ph": "Sekcem awal n uɛeddi",
+       "yourpasswordagain": "Ɛiwed ssekcem awal n uɛeddi",
+       "createacct-yourpasswordagain": "Sentem awal n uɛeddi",
+       "createacct-yourpasswordagain-ph": "Sekcem awal n uɛeddi tikelt nniḍen",
+       "userlogin-remembermypassword": "Eǧǧ taɣimit inu turmidt",
+       "userlogin-signwithsecure": "Seqdec tuqqna yettwaḥerzen",
+       "cannotlogin-title": "Ur izmir ara ad yeqqen",
+       "cannotlogin-text": "Tuqqna d tawezɣit.",
+       "cannotloginnow-title": "Ur izmir ara ad yeqqen tura",
+       "cannotloginnow-text": "Tuqqna d tawezɣit ticki tseqdaceḍ $1.",
+       "cannotcreateaccount-title": "Ur izmir ara ad yernu imiḍanen",
+       "cannotcreateaccount-text": "Timerna srid n imiḍanen n iseqdacen ur termid ara ɣef uwiki-agi.",
+       "yourdomainname": "Taɣult inek",
+       "password-change-forbidden": "Ur zemreḍ ara ad beddeleḍ awalen n uɛaddi ɣef uwiki agi.",
+       "externaldberror": "Yella ugul aberrani n database neɣ ur tettalaseḍ ara ad tbeddleḍ isem an wemseqdac aberrani inek.",
+       "login": "Kcem",
+       "login-security": "Senqed timagit-ik",
+       "nav-login-createaccount": "Kcem / Xleq isem n wemseqdac",
+       "logout": "Ffeɣ",
+       "userlogout": "Ffeɣ",
+       "notloggedin": "Ur tekcimeḍ ara",
+       "userlogin-noaccount": "Ur tesɛiḍ ara amiḍan ?",
+       "userlogin-joinproject": "Ddukkel ar {{SITENAME}}",
+       "createaccount": "Xleq amivan",
+       "userlogin-resetpassword-link": "Ettuḍ awal n uɛaddi ?",
+       "userlogin-helplink2": "Tallelt i tuqqna",
+       "userlogin-loggedin": "Teqqneḍ yakan am {{GENDER:$1|$1}}. Seqdec tiferkit ddaw-agi iwakken ad teqqneḍ s umiḍan nniḍen.",
+       "userlogin-reauth": "Snulfud amiḍan nniḍen",
+       "userlogin-createanother": "Snulfud amiḍan nniḍen",
+       "createacct-emailrequired": "Tansa email",
+       "createacct-emailoptional": "Tansa email (axetṛan)",
+       "createacct-email-ph": "Sekcem tansa email inek(m)",
+       "createacct-another-email-ph": "Sekcem tansa email",
+       "createaccountmail": "Seqdec awal n uɛaddi agacuran akudan dɣa ceggeε-it ar tansa email yemlen",
+       "createacct-realname": "Isem n tidets (axetṛan)",
+       "createacct-reason": "Taɣẓint",
+       "createacct-reason-ph": "Ayɣer ad snulfuḍ amiḍan nniḍen",
+       "createacct-reason-help": "Izen yettwaseknen deg uɣmis n tmerna n umiḍan",
+       "createacct-submit": "Rnu amiḍan-ik(m)",
+       "createacct-another-submit": "Rnu amiḍan",
+       "createacct-continue-submit": "Kemmel timerna n umiḍan",
+       "createacct-another-continue-submit": "Kemmel timerna n umiḍan",
+       "createacct-benefit-heading": "{{SITENAME}} yetwexdem sɣur medden am kečč/kem.",
+       "createacct-benefit-body1": "{{PLURAL:$1|abeddel|ibeddilen}}",
+       "createacct-benefit-body2": "{{PLURAL:$1|asebter|isebtar}}",
+       "createacct-benefit-body3": "{{PLURAL:$1|atekki amaynut|ittekkiyen imaynuten}}",
+       "badretype": "Awal n tbaḍnit amezwaru d wis sin mačči d kif-kif.",
+       "userexists": "Isem n wemseqdac yeddem-as amdan wayeḍ. Fren yiwen nniḍen.",
+       "loginerror": "Agul n ukcam",
+       "createacct-error": "Tuccda deg usnulfu n umiḍan",
+       "createaccounterror": "Ulamek ad nesnulfu amiḍan: $1",
+       "nocookiesnew": "Isem n wemseqdac-agi yettwaxleq, meɛna ur tekcimeḍ ara. {{SITENAME}} yesseqdac ikukiyen (cookies) iwakken ad tkecmeḍ. Tekseḍ ikukiyen-nni. Eǧǧ-aten, umbeɛd kecm s yisem n wemseqdac akk d wawal n tbaḍnit inek.",
+       "nocookieslogin": "{{SITENAME}} yesseqdac ikukiyen (cookies) iwakken ad tkecmeḍ. Tekseḍ ikukiyen-nni. Eǧǧ-aten iwakken ad tkecmeḍ.",
+       "nocookiesfornew": "Amiḍan n useqdac ur d-isnulfu ara, acku ur nezmer ara an sulu azar-is.\nSelken ma sermedeḍ \"cookies\", sismeḍ asebter dɣa εreḍ tikkelt nniḍen.",
+       "noname": "Ur tefkiḍ ara isem n wemseqdac ṣaḥiḥ.",
+       "loginsuccesstitle": "Taqqneḍ",
+       "loginsuccess": "<strong>Tkecmeḍ ar {{SITENAME}} s yisem n wemseqdac \"$1\".</strong>",
+       "nosuchuser": "Aseqdac $1 ulac-it d-agi.\nIsmawen n iseqdacen ṭṭafaṛen taruẓi n usekkil.\nSenqed tira, neɣ [[Special:CreateAccount|rnu amiḍan amaynut]].",
+       "nosuchusershort": "Ulac isem n wemseqdac s yisem \"$1\". Ssenqed tira n yisem-nni.",
+       "nouserspecified": "Yessefk ad tefkeḍ isem n wemseqdac.",
+       "login-userblocked": "Aseqdac agi i sewḥel. Tuqqna t-ugwi.",
+       "wrongpassword": "Awal n tbaḍnit ɣaleṭ. Ɛreḍ daɣen",
+       "wrongpasswordempty": "Awal n tbaḍnit ɣaleṭ. Ɛreḍ daɣen",
+       "passwordtooshort": "Awal-ik (im) n uɛaddi ilaq ad i sɛu adday {{PLURAL:$1|1 asekkil|$1 isekkilen}}.",
+       "password-name-match": "Ilaq awal n uɛaddi ad yili imeẓli s-isem n useqdac.",
+       "password-login-forbidden": "aseqdac agi d awal n uɛaddi agi d-izenbigen.",
+       "mailmypassword": "Awennez tikkelt nniḍen n awal uɛaddi",
+       "passwordremindertitle": "Asmekti n wawal n tbaḍnit seg {{SITENAME}}",
+       "passwordremindertext": "Amdan (waqila d kečč/kem, seg tansa IP $1) yesteqsa iwakken a nazen\nAwal n uɛaddi amaynut i {{SITENAME}} ($4). Awal n uɛaddi i wemseqdac \"$2\" yuɣal-d tura \"$3\".\nMliḥ lukan tkecmeḍ u tbeddleḍ Awal n uɛaddi tura.\nTasewti n awal agi n uɛaddi amaynut ad yaweḍ deg {{PLURAL:$5|yiwen ass|$5 ussan}}\n\nLukan mačči d kečč i yesteqsan naɣ tecfiḍ ɣef awal n uɛaddi, tzemreḍ ad tkemmleḍ mebla ma tbeddleḍ awal n uɛaddi.",
+       "noemail": "\"$1\" ur yesɛi ara email.",
+       "noemailcreate": "Ilaq ad efkeḍ tansa e-mail i sɛan aseɣbel.",
+       "passwordsent": "Awal n tbaḍnit amaynut yettwazen i emal inek, aylaw n \"$1\".\nG leɛnaya-k, kcem tikelt nniḍen yis-s.",
+       "blocked-mailpassword": "Tansa-ik IP tewḥel i unifel. Akken ad nsewḥel yir aseqdec, ur tezmireḍ ara ad tesqedceḍ tiririt n wawal uffir si tansa-agi IP.",
+       "eauthentsent": "Yiwen email yetweceggeε ar tansa id efkeḍ.\nUqbel ad n-ceggeε email nniḍen, ilaq ad ḍfereḍ ayen yellan deg email dɣa ad sergegeḍ amiḍan agi d win inek(m).",
+       "throttled-mailpassword": "Neceggɛed yakan tirawt n uwennez i awal-ik/im n uɛaddi deg {{PLURAL:$1|asrag agi aneggaru|$1 isragen agi ineggura}}. Awennez n uwal n uɛaddi yettwaceggaɛ tikelt kan deg {{PLURAL:$1|asrag|$1 isragen}}.",
+       "mailerror": "Agul asmi yettwazen e-mail: $1",
+       "acct_creation_throttle_hit": "Inebgawen iseqdacen tansa IP-ik rnan {{PLURAL:$1|n umiḍan|$1 n imiḍanen}} deg $2 n yisragen ineggura, d ayen yessawḍen ar talast tafellayt yettwasirgen deg uzilal-agi n wakud.\nIhi, timerna n imiḍanen i yinebgawen iseqdacen tansa-agi IP tewḥel akka tura.",
+       "emailauthenticated": "Tansa-ik(im) imayl tettwasentem di $2 af $3.",
+       "emailnotauthenticated": "Tansa email inek mazal ur tettuɛqel. Ur d netceggaɛ ara email i yal tiseɣnin agi.",
+       "noemailprefs": "Efk tansa e-mail iwakken ad leḥḥun iḍaɣaren-nni.",
+       "emailconfirmlink": "Sentem tansa-ik imayl",
+       "invalidemailaddress": "Tansa e-mail-agi ur telhi, ur tesɛi ara taseddast n lɛali. Ssekcem tansa e-mail s taseddast n lɛali neɣ ur tefkiḍ acemma.",
+       "cannotchangeemail": "Ur t-zemreḍ ara ad beddeleḍ tansa e-mail deg uwiki agi.",
+       "emaildisabled": "Asmel agi ur yezmer ara ad i cegaɛ e-mail.",
+       "accountcreated": "Isem n wemseqdac yettwaxleq",
+       "accountcreatedtext": "Amiḍan n useqdac i [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|asqerdec]]) yettwarna.",
+       "createaccount-title": "Asnulfu n umiḍan i {{SITENAME}}",
+       "createaccount-text": "Albeɛḍ yesnulfu-d amiḍan i tansa e-amil inek/inem ɣef {{SITENAME}} ($4) s-isem n-useqdac « $2 », s awal n uɛaddi « $3 ».\nIlaq tura ad lldiḍ taɣimit dɣa ad beddeleḍ awal ik/im n uɛaddi.",
+       "login-throttled": "Tɛerdeḍ ad qqeneḍ aṭas tiqwal deg dqiqat agi iɛddan.\nIlaq ad rǧuḍ $1 uqbel ad ɛerdeḍ tikkelt nniḍen.",
+       "login-abort-generic": "Awraḍ-ik n tuqqna ur yeddi ara",
+       "login-migrated-generic": "Amiḍan-ik iguǧ, isem-ik n useqdac qerǧin yella deg uwiki-agi.",
+       "loginlanguagelabel": "Tutlayt: $1",
+       "suspicious-userlogout": "Asuter n usenser yugwi acku yella ugur s iminig naɣ s tazarkatut n uqeddac proxy.",
+       "createacct-another-realname-tip": "Isem n ṣṣeḥ d-axeṭran.\nMa teɛzemeḍ a t-tefkeḍ, ad yettuseqdac iwakken ad snen medden anwa yuran tikkin inek.",
+       "pt-login": "Qqen",
+       "pt-login-button": "Qqen",
+       "pt-login-continue-button": "Kemmel tuqqna",
+       "pt-createaccount": "Rnu amiḍan",
+       "pt-userlogout": "Ffeɣ",
+       "php-mail-error-unknown": "anezri warisem deg tawuri mail() n PHP",
+       "user-mail-no-addy": "Ɛred ad icegaɛ e-mail war tansa e-mail",
+       "user-mail-no-body": "Arram n uceggaɛ email s tafekka tilemt neɣ d-awezlan aṭas.",
+       "changepassword": "Beddel awal n tbaḍnit",
+       "resetpass_announce": "Akken ad tfakeḍ ajerred-ik, yessefk ad tmuddeḍ awal uffir amaynut.",
+       "resetpass_header": "Beddel awal n uɛassi n umiḍan",
+       "oldpassword": "Awal n tbaḍnit aqdim:",
+       "newpassword": "Awal n tbaḍnit amaynut:",
+       "retypenew": "Ɛiwed ssekcem n tbaḍnit amaynut:",
+       "resetpass_submit": "Eg awal n tbaḍnit u kcem",
+       "changepassword-success": "Awal-ik uffir ibeddel!",
+       "changepassword-throttled": "Tɛerdeḍ ad qqeneḍ aṭas tiqwal deg dqiqat agi iɛddan.\nIlaq ad rǧuḍ $1 uqbel ad ɛerdeḍ tikkelt nniḍen.",
+       "botpasswords": "Iwalen uffiren n iṛubuten",
+       "botpasswords-disabled": "Awalen uffiren n iṛubuten nsan.",
+       "botpasswords-no-central-id": "Akken ad tesqedceḍ awalen uffiren n  iṛubuten, yessefk ad teqqneḍ amiḍan yettwaslemsen.",
+       "botpasswords-existing": "Awalen uffiren n uṛubuten yellan",
+       "botpasswords-createnew": "Rnu awal uffir n iṛubuten amaynut",
+       "botpasswords-editexisting": "Ẓreg awal uffir n iṛubuten yellan",
+       "botpasswords-label-appid": "Isem n uṛubut:",
+       "botpasswords-label-create": "Rnu",
+       "botpasswords-label-update": "Leqqem",
+       "botpasswords-label-cancel": "Sefsex",
+       "botpasswords-label-delete": "Kkes",
+       "botpasswords-label-resetpassword": "Wennez awal uffir",
+       "botpasswords-label-grants": "Izerfan yettwasnasen:",
+       "botpasswords-label-grants-column": "Ttunefken izerfan",
+       "botpasswords-bad-appid": "Isem n uṛubut \"$1\" mačči d ameɣtu.",
+       "botpasswords-insert-failed": "Ur isaweḍ ara ad yernu isem n uṛubut \"$1\". Yettwarna yakan?",
+       "botpasswords-update-failed": "Maqqdesh leqqem isen n uṛubut ''$1''. Rah Kkes ?",
+       "botpasswords-created-title": "Awal uffir n iṛubuten yettwarna",
+       "botpasswords-created-body": "Awal n uâddi n iṛubuten l isem n uṛubut ''$1'' n {{GENDER:$2|useqdac}} ''$2'' rnu",
+       "botpasswords-updated-title": "Awal n uâddi n iṛbuten ibeddel",
+       "botpasswords-updated-body": "Awal n uâddi n iṛubuten l isem n uṛubut ''$1'' n {{GENDER:$2|useqdac}} ''$2'' ibeddel",
+       "botpasswords-deleted-title": "Awal uffir n iṛubuten yettwakkes",
+       "botpasswords-deleted-body": "Awal n uâddi n uṛubut \"$1\" n {{GENDER:$2|useqdac}} \"$2\" yettwakkes.",
+       "botpasswords-no-provider": "Asaǧǧaw n tɣimit n wawal n uâddi n iṛubuten ulac-it.",
+       "resetpass_forbidden": "Ur zemreḍ ara ad beddeleḍ awalen n uɛaddi",
+       "resetpass_forbidden-reason": "Ur tezmireḍ ara ad tẓergeḍ awal uffir : $1",
+       "resetpass-no-info": "Ilaq ad qqeneḍ iwakken ad ẓṛeḍ asebter agi.",
+       "resetpass-submit-loggedin": "Beddel awal n uɛaddi",
+       "resetpass-submit-cancel": "Semmewet",
+       "resetpass-wrong-oldpass": "Awal n uɛaddi d askudan neɣ amira mačči d ameɣtu.\nAhat tbeddleḍ yakanawal-ik uffir neɣ tsutreḍ-d askudan-nniḍen.",
+       "resetpass-temp-password": "Awal n uɛaddi amakud",
+       "resetpass-abort-generic": "Asiɣzef yesemmewet abeddel n uwal n uɛaddi.",
+       "passwordreset": "Awennez tikkelt nniḍen n awal uɛaddi",
+       "passwordreset-text-one": "Ččur tiferkit agi iwakken ad wennezeḍ awal-ik/im n uɛaddi.",
+       "passwordreset-text-many": "{{PLURAL:$1|Čcur yiwet n tiɣwezza iwakken ad rmeseḍ awal n uɛaddi uɛḍil deg tirawt.}}",
+       "passwordreset-disabled": "Awennez n awal uɛaddi yensa deg uwiki agi.",
+       "passwordreset-emaildisabled": "Tiseɣnin email nsant ɣef wiki agi.",
+       "passwordreset-username": "Isem n useqdac:",
+       "passwordreset-domain": "Talɣut :",
+       "passwordreset-email": "Tansa e-mail:",
+       "passwordreset-emailtitle": "Tilɣa n umiḍan ɣef {{SITENAME}}",
+       "passwordreset-emailtext-ip": "Yiwen (Ahat kečč/kem, seg tansa IP $1) yessutered awennez n awal n uɛaddi i {{SITENAME}} ($4). {{PLURAL:$3|Amiḍan n useqdac agi yeqqen|imiḍanen n iseqdacen agi qqenen}} s tansa e-mail agi :\n\n$2\n\n{{PLURAL:$3|Awal n uɛaddi uɛḍil agi ad i aff tasewti-s|Awalen n uɛaddi uɛḍilen agi ad affen taseweti nsen}} deg {{PLURAL:$5|yiwen ass|$5 ussan}}. Ilaq tura ad qqeneḍ dɣa ad freneḍ awal n uɛaddi amaynut. Lukan mačči d kečč/kem i xedmen asuter agi, naɣ tecfiḍ tura i awal n uɛaddi inek/inem, tzemreḍ ad eǧǧeḍ izen agi.",
+       "passwordreset-emailtext-user": "Aseqdac $1 ɣef {{SITENAME}} yessutered awennez n awal n uɛaddi i {{SITENAME}} ($4). {{PLURAL:$3|Amiḍan n useqdac agi yeqqen|imiḍanen n iseqdacen agi qqenen}} s tansa e-mail agi :\n\n$2\n\n{{PLURAL:$3|Awal n uɛaddi uɛḍil agi ad i aff tasewti-s|Awalen n uɛaddi uɛḍilen agi ad affen taseweti nsen}} deg {{PLURAL:$5|yiwen ass|$5 ussan}}. Ilaq tura ad qqeneḍ dɣa ad freneḍ awal n uɛaddi amaynut. Lukan mačči d kečč/kem i xedmen asuter agi, naɣ tecfiḍ tura i awal n uɛaddi inek/inem, tzemreḍ ad eǧǧeḍ izen agi.",
+       "passwordreset-emailelement": "Isem n useqdac : \n$1\n\nAwal n uɛddi akudan : \n$2",
+       "passwordreset-emailsentemail": "Ma yella imayl-agi icudd ar umiḍan-ik, awennez n wawal uffir ad yettwazen ar yimayl.",
+       "passwordreset-invalidemail": "Tansa imayl d tarameɣtut",
+       "changeemail": "Beddel neɣ kkes tansa n imayl",
+       "changeemail-no-info": "Ilaq ad qqeneḍ iwakken ad ẓṛeḍ asebter agi.",
+       "changeemail-oldemail": "Tansa e-mail n tura:",
+       "changeemail-newemail": "Tansa e-mail tamaynut:",
+       "changeemail-newemail-help": "Laissez ce champ vide si vous voulez supprimer votre adresse de courriel. Sans adresse de courriel renseignée, vous ne pourrez plus réinitialiser votre mot de passe en cas d’oubli ni recevoir de courriels à partir de ce wiki.",
+       "changeemail-none": "(ulac)",
+       "changeemail-password": "Awal-ik/im n uɛaddi ɣef {{SITENAME}}:",
+       "changeemail-submit": "Beddel tansa e-mail",
+       "changeemail-throttled": "Tɛerdeḍ ad qqeneḍ aṭas tiqwal.\nIlaq ad rǧuḍ $1 uqbel ad ɛerdeḍ tikkelt nniḍen.",
+       "changeemail-nochange": "Ttxilek sekcem tansa i imayl nniḍen.",
+       "resettokens": "Wennez tiddas",
+       "resettokens-text": "D-agi tzemreḍ ad twennezeḍ tiddas i ɛemmeden ad kecmeḍ ar isefka usligen i qqenen ar amiḍan inek/inem.\n\nIlaq ad twennezeḍ tiddas ma tferqeḍ-ten s tuccḍa s umseqdac nniḍen neɣ ma amiḍan inek/inem yexṣer.",
+       "resettokens-no-tokens": "Ulac tiddas an wennez.",
+       "resettokens-tokens": "Tiddas:",
+       "resettokens-token-label": "$1 (azal amiran: $2)",
+       "resettokens-watchlist-token": "Tiddest i usuddem (Atom/RSS) web n [[Special:Watchlist|ibeddilen n isebtar n umuɣ inek/inem n uḍfar]]",
+       "resettokens-done": "Tiddas i wennezen.",
+       "resettokens-resetbutton": "Wennez tiddas i fernen",
+       "bold_sample": "Aḍris aberbuz",
+       "bold_tip": "Aḍris aberbuz",
+       "italic_sample": "Aḍris aṭalyani",
+       "italic_tip": "Aḍris aṭalyani",
+       "link_sample": "Azwel n uzday",
+       "link_tip": "Azday zdaxel",
+       "extlink_sample": "http://www.example.com azwel n uzday",
+       "extlink_tip": "Azday aberrani (cfu belli yessefk at tebduḍ s http://)",
+       "headline_sample": "Aḍris n uzwel azellum",
+       "headline_tip": "Aswir 2 n uzwel azellum",
+       "nowiki_sample": "Sideff da tirra bla taseddast(formatting) n wiki",
+       "nowiki_tip": "Ttu taseddast n wiki",
+       "image_tip": "Tugna yettussekcmen",
+       "media_tip": "Azday n ufaylu media",
+       "sig_tip": "Azmul inek s uzemz",
+       "hr_tip": "Ajerriḍ aglawan (ur teččerɛiḍ ara)",
+       "summary": "Agzul:",
+       "subject": "Asentel:",
+       "minoredit": "Wagi d abeddel afessas",
+       "watchthis": "Ɛass asebter-agi",
+       "savearticle": "Beddel asebter",
+       "savechanges": "Sekles asnifel",
+       "publishpage": "Suffeɣ-d asebter",
+       "publishchanges": "Suffeɣ-d asnifel",
+       "preview": "azar-asekdan",
+       "showpreview": "Ssken azar-asekdan",
+       "showdiff": "Ssken ibeddlen",
+       "blankarticle": "<strong>Ɣur-ek:</strong> Asebter d-tesɣenweḍ d ilem.\nMayel tkketaḍ daɣen ɣef \"$1\", ad iɣnew usebter war agbur.",
+       "anoneditwarning": "<strong>Ɣur-k:</strong> ur teqqineḍ ara. Tansa-ik IP ad d-ban i yal yiwen ma yella ur teggiḍ ara abeddel. Ma yella <strong>[$1 teqqneḍ]</strong> neɣ <strong>[$2 rnu amiḍan]</strong>, abeddel-ik ad ittusemmi s yisem-ik, s ufareṣ n tignatin-nniḍen.",
+       "anonpreviewwarning": "<em>Ur tesuluḍ ara. Aḥraz ad yekles tansa IP inek/inem deg umezruy n ibeddilen n usebter.</em>",
+       "missingsummary": "<strong>Ur tettuḍ ara:</strong> Ur tefkiḍ ara azwel i ubeddel inek. Lukan twekkiḍ ''Smekti'' ''$1'' tikelt nniḍen, abeddel inek ad yettusmekti mebla azwel.",
+       "missingcommenttext": "Ssekcem awennit deg ukessar.",
+       "missingcommentheader": "<strong>Asmekti:</strong> ur d-muddeḍ ara asentel n uwennit-agi.\nMa tsenndeḍ tikelt-nniḍen ɣef \"$1\", abeddel-ik ad yettwasekles s war asentel.",
+       "summary-preview": "Taskant n ugzul n ubeddel:",
+       "subject-preview": "Taskant n usentel:",
+       "blockedtitle": "Amseqdac iɛekkel",
+       "blockedtext": "<strong>Amiḍan-ik n useqdac neɣ tansa n IP weḥlen.</strong>\n\nAsewḥel yega-t $1.\nTaɣẓint d tagi : <em>$2</em>.\n\n* Tazwara n usewḥel : $8\n* Tagara n usewḥel : $6\n* Amiḍan iweḥlen : $7.\n\n\nTzemreḍ ad tnermseḍ $1 neɣ [[{{MediaWiki:Grouppage-sysop}}|anedbal]]-nniḍen akken ad tsmelayem.\nUr tezmireḍ ara ad tesqedceḍ tawuri \"nemres aseqdac-agi\" ma yella tella tensa tameɣtut deg[[Special:Preferences|ismenyifen n useqdac]], udiɣ ma yella tawuri-agi ur tewḥil ara. Tansa-ik IP n tura d $3, ID n usewḥeld #$5.\nSuddu-d akk talɣut-agi deg tuttriwin ara d-azdneḍ.",
+       "autoblockedtext": "Tansa-ik IP tewḥel s wudem awurman acku i tt-yesseqdacen  yesweḥli-t $1.\n\n\nTaɣẓint d tagi :\n:<em>$2</em>\n\n* Tazwara n usewḥel : $8\n* Tagara n usewḥel : $6\n* Amiḍan iweḥlen : $7.\n\n\nTzemreḍ ad tnermseḍ $1 neɣ [[{{MediaWiki:Grouppage-sysop}}|anedbal]]-nniḍen akken ad tsmelayem.\nUr tezmireḍ ara ad tesqedceḍ tawuri \"nermres aseqdac-agi\" ma yella tella tensa tameɣtut deg[[Special:Preferences|ismenyifen n useqdac]], udiɣ ma yella tawuri-agi ur tewḥil ara. \n\nTansa-ik IP n tura d $3, ID n usewḥeld #$5.\nSuddu-d akk talɣut-agi deg tuttriwin ara d-azdneḍ.",
+       "blockednoreason": "Ulac taɣẓint",
+       "whitelistedittext": "Yessefk ad $1 iwakken ad tbeddleḍ isebtar.",
+       "confirmedittext": "Yessefk ad tsentmeḍ tansa e-mail inek uqbel abeddel. Xtar tansa e-mail di [[Special:Preferences|isemyifiyen n wemseqdac]].",
+       "nosuchsectiontitle": "Ulamek an af tigezmi",
+       "nosuchsectiontext": "Tɛerḍeḍ ad tbeddleḍ tigezmi ur llan ara.",
+       "loginreqtitle": "Yessefk ad tkecmeḍ",
+       "loginreqlink": "Kcem",
+       "loginreqpagetext": "Yessefk $1 iwakken ad teẓriḍ isebtar wiyaḍ.",
+       "accmailtitle": "Awal n uɛaddi yettwazen.",
+       "accmailtext": "Awal n uɛaddi id yuran s ugacur i [[User talk:$1|$1]] yetweceggaɛ i $2.\nYezmer ad yetbeddel ɣef usebtar [[Special:ChangePassword|Abeddel n awal uɛddi]] sakin tuqqna.",
+       "newarticle": "(Amaynut)",
+       "newarticletext": "Tḍefreḍ azday ɣer usebter mazal ur yettwaxleq ara.\nAkken ad txelqeḍ asebter-nni, aru deg tenkult i tella deg ukessar\n(ẓer [$1 asebter n tallalt] akken ad tessneḍ kter).\nMa tɣelṭeḍ, wekki kan ɣef tqeffalt \"Back/Précédent\" n browser/explorateur inek.",
+       "anontalkpagetext": "----\n<em>Aqla-k deg usebter n usqerdec n useqdac udrig ur yernan ara yakan amiḍan neɣ ur t-yesseqdacen ara </em>.\nƔef aya, yessefk ad nseqdec tansa-is IP akken ad t-nissin.\nTansa IP tezmer ad tettwabḍu sɣuṛ ddeqs n iseqdacen.\nMa telliḍ d {{GENDER:|||}} d {{GENDER:|aseqdac udrig|taseqdact tudrigt}} udiɣ twalaḍ d akken iwenniten ur k-neɛni ara ttwaznen-ak-d, tzemreḍ [[Special:CreateAccount|ad ternuḍ amiḍan]] neɣ [[Special:UserLogin|ad teqqneḍ]] akken ad tzegleḍ akk ar zdat anexluḍ akked imttekkiyen udrigen.",
+       "noarticletext": "Ulac aḍris deg usebter-agi akka tura Tzemreḍ [[Special:Search/{{PAGENAME}}|ad tnadiḍ aswel n usebter-agi]] deg isebtar-nniḍen,<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} nadi ti temhalin icudden ɣur-s],\nneɣ [{{fullurl:{{FULLPAGENAME}}|action=edit}} rnu asebter-agi]</span",
+       "noarticletext-nopermission": "Imira ulac aḍris deg usebter agi.\nTzemreḍ [[Special:Search/{{PAGENAME}}|ad nadiḍ ɣef azwel agi]] deg isebtaren nniḍen,\nnaɣ <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|asebter={{FULLPAGENAMEE}}}} ad nadiḍ deg iɣmisen iqqenen]</span>.",
+       "missing-revision": "Tacaggart #$1 n usebter s isem « {{FULLPAGENAME}} » ulac-itt.\n\nAcku azday n umezruy, ɣef wayen tsennedeḍ, d-aqbur. Asebter yemḥa.\nTzemreḍ ad affeḍ tilɣa deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n isebtar yemḥan].",
+       "userpage-userdoesnotexist": "Amiḍan n useqdac « <nowiki>$1</nowiki> » ur yekles ara. Ilaq ad selkeneḍ ma tebɣiḍ ad snulfuḍ asebter agi.",
+       "userpage-userdoesnotexist-view": "Amiḍan n useqdac ''$1'' ur yekles ara.",
+       "blocked-notice-logextract": "Aseqdac agi yekyef.\nAsekcem aneggaru n useklas n ikyafen yella ddaw agi:",
+       "clearyourcache": "<strong>Tamawt :</strong> deffir mi nsekles ibeddilen-ik, yessefk ad tḥettmeḍ asmiren ummid n tuffirt n yiminig-ikakken ad twaliḍ ibeddilen.\n* <strong>Firefox / Safari :</strong> eǧǧ afus-ik ɣef taqeffalt <em>Maj</em> (<em>Shift</em>) sakin senned ɣef tqeffalt <em>Smiren</em> neɣ senned <em>Ctrl-F5</em> neɣ <em>Ctrl-R</em> (<em>⌘-R</em> di Mac) \n* <strong>Google Chrome :</strong> senned ɣef  <em>Ctrl-Maj-R</em> (<em>⌘-Shift-R</em> di Mac) \n* <strong>Internet Explorer :</strong> senned ɣef tqeffalt  <em>Ctrl</em> udiɣ ɣef tqeffalt <em>Smiren</em> neɣ senned ɣef <em>Ctrl-F5</em> \n* <strong>Opera :</strong> ddu ar wumuɣ <em>Umuɣ → Iɣewwaṛen</em> (<em>Opera → Ismenyifen</em> ɣef Mac) sakin ɣef <em>Tabaḍnit & taɣellit → Sfeḍ isefka n usnirem → Tugniwin akked d iful=yla n tuffirt</em>.",
+       "usercssyoucanpreview": "<strong>taxbalut:</strong> Sseqdec taqeffalt « {{int:showpreview}} » iwakken ad tɛerḍeḍ asebter CSS inek/inem amaynut  uqbel ad aklasteḍ.",
+       "userjsonyoucanpreview": "<strong>taxbalut:</strong> Sseqdec taqeffalt « {{int:showpreview}} » iwakken ad tɛerḍeḍ asebter JSON inek/inem amaynut  uqbel ad aklasteḍ.",
+       "userjsyoucanpreview": "<strong>taxbalut:</strong> Sseqdec taqeffalt « {{int:showpreview}} » iwakken ad tɛerḍeḍ asebter JavaScript inek/inem amaynut  uqbel ad aklasteḍ.",
+       "usercsspreview": "<strong>Cfu-d, wagi d-azaraskan n usebter ik/im n CSS.\nMmazal ur yettusmekti ara!</strong>",
+       "userjsonpreview": "<strong>Cfu-d, wagi d-azaraskan n usebter ik/im n JSON.\nMmazal ur yettusmekti ara!</strong>",
+       "userjspreview": "<strong>Cfu-d, wagi d-azaraskan n usebter ik/im n JavaScript.\nMmazal ur yettusmekti ara!</strong>",
+       "sitecsspreview": "<strong>Smekti belli aql-ak tɛerḍeḍ asebter CSS agi inek kan.\nMazal ur yettusmekti ara!</strong>",
+       "sitejsonpreview": "<strong>Smekti belli aql-ak tɛerḍeḍ asebter JSON config agi inek kan.\nMazal ur yettusmekti ara!</strong>",
+       "sitejspreview": "<strong>Smekti belli aql-ak tɛerḍeḍ asebter JavaScript inek kan.\nMazal ur yettusmekti ara!</strong>",
+       "userinvalidconfigtitle": "<strong>Aɣtal:</strong> Aglim \"$1\" ulac-it. Ur tettuḍ ara belli isebtar \".css\" d \".json u .js\" i txedmeḍ sseqdacen azwel i yesɛan isekkilen imecṭuḥen, s umedya: {{ns:user}}:Foo/vector.css akk d {{ns:user}}:Foo/Vector.css.",
+       "updated": "(Yettubeddel)",
+       "note": "<strong>Tamawt:</strong>",
+       "previewnote": "<strong>Ttagi d azar-timeẓriwt kan, ibeddlen mazal ur ttusmektin ara!</strong>\n\nCfut, ttagi d azar-timeẓriwt kan.\nIbeddlen mazal ur ttusmektin ara!",
+       "continue-editing": "Ṛuḥ ar taɣzut n ubeddel",
+       "previewconflict": "Pre-timeẓriwt-agi tesskan aḍris i yellan deg usawen lemmer tebɣiḍ a tt-tesmektiḍ.",
+       "session_fail_preview": "Suref-aɣ! ur nezmir ara a nesmekti abeddil inek axaṭer yella ugur.\nG leɛnayek ɛreḍ tikelt nniḍen. Lukan mazal yella ugur, ffeɣ umbeɛd kcem [[Special:UserLogout|logging out]].",
+       "session_fail_preview_html": "Ur nezmer ara an aklas ibeddilen inek/inem acku yella asṛuḥu n tilɣa deg taɣimit inek/inem.\n\n<em>Acku {{SITENAME}} i sermed azar n HTML, azaraskan yeseggelmes iwakken ur t-illint ara tinṭagin s Javascript.</em>\n\n<strong>Lukan abeddel agi d-aḥeqqani, ɛered tikkelt nniḍen.</strong>\nLukan yella ugur, [[Special:UserLogout|Senser]] dɣa qqen.",
+       "token_suffix_mismatch": "<strong>Abeddel inek/inem ur yeɣbel ara acku iminig inek/inem ur yesettengel ara s umellil isekkilen n uqqa deg asulay n ubeddel.</strong>\nTiririt agi telaq i usḍiqqef n usgufsu n uḍris deg usebter.\nUgur agi, yetilli tikwal mi seqdeceḍ aqeddac Proxy warisem yellan ɣef Web.",
+       "edit_form_incomplete": "<strong>Kra n iḥricen n tiferkit n ubeddel ur gweḍen ara ar uqeddac, ilaq ad selkeneḍ ma ibeddilen ur erẓen ara dɣa ɛreḍ tikkelt nniḍen.</strong>",
+       "editing": "Aglaf n $1",
+       "creating": "Asnulfu n $1",
+       "editingsection": "Aglaf n $1 (tagzemt)",
+       "editingcomment": "Abeddel n $1 (tigezmi tamaynut)",
+       "editconflict": "Amennuɣ deg ubeddel: $1",
+       "explainconflict": "Amdan nniḍen ibeddel asebter-agi asmi telliḍ tettbeddileḍ.\nAḍris deg usawen yesɛa asebter am yella tura.\nIbeddlen inek ahaten deg ukessar.\nYesfek ad txelṭeḍ ibeddlen inek akk d usebter i yellan.\n<strong>Ala</strong> aḍris deg usawen i yettusmekta asmi twekkiḍ \"$1\".",
+       "yourtext": "Aḍris inek",
+       "storedversion": "Tasiwelt yettusmketen",
+       "editingold": "<strong>AƔTAL</strong> Aqlak tettbeddileḍ tasiwelt taqdimt n usebter-agi.\nMa ara t-tesmektiḍ, akk ibeddlen i yexdmen seg tasiwelt-agi ruḥen.",
+       "yourdiff": "Imgerraden",
+       "copyrightwarning": "Ssen belli akk tikkin deg {{SITENAME}} hatent ttwaznen seddaw $2 (Ẓer $1 akken ad tessneḍ kter). Lukan ur tebɣiḍ ara aru inek yettubeddel neɣ yettwazen u yettwaru deg imkanen nniḍen, ihi ur t-tazneḍ ara dagi.<br />\nAqlak teggaleḍ belli tureḍ wagi d kečč, neɣ teddmiḍ-t seg taɣult azayez neɣ iɣbula tilelliyin.\n<strong>UR TEFKIḌ ARA AXDAM S COPYRIGHT MEBLA TURAGT!</strong>",
+       "copyrightwarning2": "Ssen belli akk tikkin deg {{SITENAME}} zemren ad ttubeddlen neɣ ttumḥan sɣur imdanen wiyaḍ. Lukan ur tebɣiḍ ara aru inek yettubeddel neɣ yettwazen u yettwaru deg imkanen nniḍen, ihi ur t-tazneḍ ara dagi.<br />\nAqlak teggaleḍ belli tureḍ wagi d kečč, neɣ teddmiḍ-t seg taɣult azayez neɣ iɣbula tilelliyin (ẓer $1 akken ad tessneḍ kter).\n<strong>UR TEFKIḌ ARA AXDAM S COPYRIGHT MEBLA TURAGT!</strong>",
+       "longpageerror": "<strong>Anezri : Aḍris i sekcemeḍ yeɛbeṛ {{PLURAL:$1|yiwen kilobyte|$1 kilobytes}}, tiddi-yagi kter n talast yellan af {{PLURAL:$2|yiwen kilobyte|$1 kilobytes}}.</strong>\nUr yezmer ara ad yetwaḥrez.",
+       "readonlywarning": "<strong>ƔUR-WET : taffa n isefka t-sekkweṛ i timhelin n ibeddi. Ur tzemreḍ ara ad ḥrezeḍ  ibeddilen tura.</strong>\nTzemreḍ ad nɣeleḍ aḍris ik/im deg ufaylu iwakken ad tesqedceḍ sakin.\n\nAnedbal i sekkweṛen taffa n isefka agi, yefka-d taɣẓint agi : $1",
+       "protectedpagewarning": "<strong>ƔUR-WET : Asebter-agi yettwaḥrez, inedbalen kan i zemren a t-beddlen.</strong>\nAsekcem aneggaru n uɣmis yella ddaw-agi:",
+       "semiprotectedpagewarning": "<strong>Tamawt:</strong> Asebter-agi yettwaḥrez, iseqdacen yesɛan amiḍan kan i zemren a t-beddlen.\nAsekcem aneggaru n uɣmis yella ddaw-agi:",
+       "cascadeprotectedwarning": "<strong>ƔUR-WET:</strong> Asebter-agi yettwaḥrez, inedbalen kan i zemren a t-beddlen. Yettwaḥrez acku yettwassekcem  deg {{PLURAL:$1|asebter i ḥerzen agi yesɛan|isebtar i ḥerzen agi yesɛan}} « amesten s uceṛcuṛ » i sermeden :",
+       "titleprotectedwarning": "<strong>ƔUR-WET: Asebter agi yemesten, dɣa ilaq ad sɛuḍ [[Special:ListGroupRights|izerfan usligen]] iwakken at id snulfuḍ.</strong> Asekcem aneggaru n uɣmis yebeqqeḍ ddaw agi:",
+       "templatesused": "{{PLURAL:$1|Talɣa i seqdacen|Tilɣatin i seqdacen}} deg usebter agi:",
+       "templatesusedpreview": "{{PLURAL:$1|Talɣa i seqdacen|Tilɣatin i seqdacen}} deg azaraskan agi:",
+       "templatesusedsection": "{{PLURAL:$1|Talɣa i seqdacen|Tilɣatin i seqdacen}} deg tigezmi agi:",
+       "template-protected": "(yettwaḥrez)",
+       "template-semiprotected": "(azin-yettwaḥrez)",
+       "hiddencategories": "Asebter agi yella deg {{PLURAL:$1|Taggayt i ffren|Tiggayin i ffren}} agi:",
+       "nocreatetext": "{{SITENAME}} yekref iẓubaẓ n usnulfu n isebtar imaynuten.\nTzemreḍ ad uɣaleḍ ar deffir dɣa ad beddeleḍ asebter yellan yakan, naɣ [[Special:UserLogin|ad qqeneḍ naɣ ad snulfuḍ amiḍan]].",
+       "nocreate-loggedin": "Ur tesɛiḍ ara turagt i usnulfu n isebtar imaynuten.",
+       "sectioneditnotsupported-title": "Abeddel n tigezmi agi ur yezmer ara",
+       "sectioneditnotsupported-text": "Abeddel n tigezmi ur yezmer ara deg usebtar agi n ubeddel.",
+       "permissionserrors": "Agul n turagt",
+       "permissionserrorstext": "Ur tesɛiḍ ara turagt iwakken ad xedmeḍ wayagi i {{PLURAL:$1|taɣẓint|tiɣẓinin}} agi:",
+       "permissionserrorstext-withaction": "Ur sɛiḍ ara ttesriḥ af $2, i {{PLURAL:$1|taɣẓint|tiɣẓinin}} agi:",
+       "recreate-moveddeleted-warn": "<strong>Ɣur-wet : asebter agi i tebɣam ad snulfum, yetwekkes uqbel.</strong>\n\nIlaq ad snulfum asebter agi haca ma i xater. Aɣmis n isebtaren i twekkesen yella ddaw-agi :",
+       "moveddeleted-notice": "Asebter-a yettwakkes. \nAɣmis n tukksa, ammesten neɣ asenkez n usebter yettwammel-d ddaw-a i uwelleh.",
+       "moveddeleted-notice-recent": "Nesḥissef, imi melmi kan i yettwakkes usebter-a (deg 24 n yisragen ineggura). Iɣmisen n tukksa, n ummesten, akked usnifel n yisem i usebter ttunefken-d ddaw-a i uwelleh.",
+       "log-fulllog": "Ẓeṛ aɣmis ummid",
+       "edit-hook-aborted": "Abrir n ubeddel s usiɣzef.\nTamentilt warisem",
+       "edit-gone-missing": "Ur yezmer ara ad yemucceḍ asebter agi.\nAhat yetwemḥa.",
+       "edit-conflict": "Amgirred n ubeddel.",
+       "edit-no-change": "Abeddel inek/inem ur yetwexdam ara acku ur di ban ara abeddel deg uḍris.",
+       "postedit-confirmation-created": "Asebter ittwarna.",
+       "postedit-confirmation-restored": "Asebter yuɣal-d.",
+       "postedit-confirmation-saved": "Abeddel inek/inem yetwakles.",
+       "edit-already-exists": "Asebter amaynut ur d yesnufu ara.\nYella yakan.",
+       "defaultmessagetext": "Izen s lexṣas",
+       "content-failed-to-parse": "Tasleṭ n ugbur n $2 i talɣa $1 texseṛ : $3",
+       "invalid-content-data": "Isefka n ugbur ur ɣbelen ara",
+       "content-not-allowed-here": "Agbur \"$1\" ur yesɛa ara turagt ɣef usebter [[$2]]",
+       "editwarning-warning": "Ma ad teffeɣeḍ seg usebter-agi ad tesṛuḥeḍ akk ibeddilen i tegiḍ.\nMa teqqeneḍ, tzemreḍ ad tsenseḍ alɣu-agi deg tigezmi \"{{int:prefs-editing}}\"  n ismenyifen-ik(im).",
+       "editpage-invalidcontentmodel-title": "Taneɣruft n ugbur ur tettwasefrak ara",
+       "editpage-invalidcontentmodel-text": "Taneɣruft n ugbur \"$1\" ur tettwasefrak ara.",
+       "editpage-notsupportedcontentformat-title": "Amasal n ugbur ur d-yetwarfed ara",
+       "editpage-notsupportedcontentformat-text": "Amasal n ugbur $1 ur d-yetwarfed ara sɣur talɣa n ugbur $2.",
+       "content-model-wikitext": "wikiaḍris",
+       "content-model-text": "aḍris afraray",
+       "content-model-javascript": "JavaScript",
+       "content-json-empty-object": "Asentel d ilem",
+       "content-json-empty-array": "Talfelwit d tilemt",
+       "deprecated-self-close-category": "Asebter iseqdacen yir tiṛekkizin HTM tumdilin tiwurmanin",
+       "expensive-parserfunction-warning": "'''Ɣur-wet :''' Asebter agi yesɛa aṭas n tiɣriwin ar tiseɣnin ɣlayen n umsisleḍ taseddast.\nIlaq ad i sɛu ddaw n  $2 {{PLURAL:$2|tiɣri|tiɣriwin }}, wannag tura {{PLURAL:$1|tella $1 tiɣri|llant $1 tiɣriwin}}.",
+       "expensive-parserfunction-category": "Isebtar yesɛan aṭas tiɣriwin ɣlayen n tiseeɣnin n umsisleḍ taseddast",
+       "post-expand-template-inclusion-warning": "<strong>Ɣur-wet:</strong> Asebter agi yesɛa aṭas tilɣatin. Kra n tilɣatin ur zemrent ara ad seqdacent.",
+       "post-expand-template-inclusion-category": "Isebtaren i sɛan aṭas tilɣatin",
+       "post-expand-template-argument-warning": "<strong>Ɣur-wet</strong>: Asebter agi yesɛa tuccḍa deg aɣewwar n yiwet talɣa.",
+       "post-expand-template-argument-category": "Isebtaren i sɛan iɣewwaren n talɣa ur skazelen ara",
+       "parser-template-loop-warning": "N-ufad talɣa s tineddict: [[$1]]",
+       "parser-template-recursion-depth-warning": "Talast n lqay n tiɣriwin n tilɣatin tefel ($1)",
+       "language-converter-depth-warning": "Talast n lqay n uselkat n tutlayt tefel ($1)",
+       "node-count-exceeded-category": "Isebtar anda amḍa n tikerwas yefel",
+       "node-count-exceeded-category-desc": "Asebter iɛedda amḍan attas afellay n tkerras",
+       "node-count-exceeded-warning": "Asebter iɛedda amḍan afellay n tkerras",
+       "expansion-depth-exceeded-category": "Isebtar anda lqay n uderrec yefel",
+       "expansion-depth-exceeded-category-desc": "Asebter iɛedda talqayt n temɣer tafellayt.",
+       "expansion-depth-exceeded-warning": "Isebtar yefelen lqay n uderrec",
+       "parser-unstrip-loop-warning": "Tifin n tineddict ur nezmer ara an sentuter",
+       "unstrip-depth-warning": "Talast n usniles ur nezmer ara an sentuter tefel ($1)",
+       "converter-manual-rule-error": "Tifin n unezri deg alugen awfus n uselket n tutlayt",
+       "undo-success": "Tzemreḍ ad tessefsuḍ abeddil. Ssenqed asidmer akken ad tessneḍ ayen tebɣiḍ ad txdmeḍ d ṣṣeḥ, umbeɛd smekti ibeddlen u tkemmleḍ ad tessefsuḍ abeddil.",
+       "undo-failure": "Ur yezmir ara ad issefu abeddel axaṭer yella amennuɣ abusari deg ubeddel.",
+       "undo-norev": "Abeddel ur yezmer ara ad yetwekkes acku ulac-itt naɣ tetwekkes yakan",
+       "undo-nochange": "Ad yettban d akken abeddel yettwasefsex yakan.",
+       "undo-summary": "Ssefsu tasiwelt $1 sɣur [[Special:Contributions/$2|$2]] ([[User talk:$2|Meslay]])",
+       "undo-summary-username-hidden": "Semmewet tacaggart $1 sɣur amseqdac yeffren",
+       "cantcreateaccount-text": "Asnulfu n umiḍan seg tansa IP (<b>$1</b>) tekyef sɣur [[User:$3|$3]].\n\nTaɣẓint n $3 : <em>$2</em>",
+       "cantcreateaccount-range-text": "Asnulfu n umiḍan seg tansiwin IP deg tagrumma <strong>$1</strong>, i sseddan tansa inek/inem IP (<strong>$4</strong>), twawḥelen sɣur [[User:$3|$3]].\n\nTaɣẓint i-d yefka/tefka $3 : <em>$2</em>",
+       "viewpagelogs": "Ẓer aɣmis n usebter-agi",
+       "nohistory": "Ulac amezruy n yibeddlen i usebter-agi.",
+       "currentrev": "Tasiwelt n tura",
+       "currentrev-asof": "Azmez n lqem taneggarut d  $1",
+       "revisionasof": "Tasiwelt n wass $1",
+       "revision-info": "Aceggir-agi yettwag di $1 sɣuṛ {{GENDER:$6|$2}}$7",
+       "previousrevision": "←Tasiwelt taqdimt",
+       "nextrevision": "Tasiwelt tamaynut→",
+       "currentrevisionlink": "Tasiwelt n tura",
+       "cur": "tura",
+       "next": "ameḍfir",
+       "last": "amgirred",
+       "page_first": "amezwaru",
+       "page_last": "aneggaru",
+       "histlegend": "Axtiri n umgerrad: rcem tankulin akken ad teẓreḍ imgerraden ger tisiwal u wekki ɣef enter/entrée neɣ ɣef taqeffalt deg ukessar.<br />\nTabadut: (tura) = amgirred akk d tasiwelt n tura,\n(amgirred) = amgirred akk d tasiwelt ssabeq, M = abeddel afessas.",
+       "history-fieldset-title": "Nadi iceggiren",
+       "history-show-deleted": "Aceggir yettwakksen kan",
+       "histfirst": "tiqdimin",
+       "histlast": "timaynutin",
+       "historysize": "({{PLURAL:$1|1 atamḍan|$1 itamḍanen}})",
+       "historyempty": "(amecluc)",
+       "history-feed-title": "Amezruy n tsiwelt",
+       "history-feed-description": "Amezruy n tsiwelt n usebter-agi deg wiki",
+       "history-feed-item-nocomment": "$1 deg $2",
+       "history-feed-empty": "Asebter i tebɣiḍ ulac-it.\nAhat yettumḥa neɣ yettbeddel isem-is.\nƐreḍ [[Special:Search|ad tnadiḍ deg wiki]] ɣef isebtar imaynuten.",
+       "history-edit-tags": "Ẓreg tirekkizin n ileqman yettwafernen",
+       "rev-deleted-comment": "(agzul n taẓrigt yettwakes)",
+       "rev-deleted-user": "(isem n wemseqdac yettwakes)",
+       "rev-deleted-event": "(talqayt n umazray tettwakkes)",
+       "rev-deleted-user-contribs": "[isem n useqdac naɣ tansa IP yetwemḥa - abeddel yeffer deg tiwsitin]",
+       "rev-deleted-text-permission": "Lqem n usebter agi <strong>tetwesfeḍ</strong>.\nTilɣa llant deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n usfeḍ].",
+       "rev-deleted-text-unhide": "Lqem n usebter agi <strong>tetwesfeḍ</strong>.\nTilɣa llant deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n usfeḍ].\nTzemreḍ meqqar [$1 ad ẓṛeḍ lqem agi]  ma tebɣiḍ",
+       "rev-suppressed-text-unhide": "Lqem n usebter agi <strong>tetwekkes</strong>.\nTilɣa llant deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n umḥu].\nTzemreḍ meqqar [$1 ad ẓṛeḍ lqem agi]  ma tebɣiḍ",
+       "rev-deleted-text-view": "Lqem n usebter agi <strong>tetwesfeḍ</strong>.\nTzemreḍ att ẓṛeḍ ; tilɣa llant deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n usfeḍ].",
+       "rev-suppressed-text-view": "Lqem n usebter agi <strong>tetwekkes</strong>.\nTzemreḍ att ẓṛeḍ ; tilɣa llant deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n umḥu].",
+       "rev-deleted-no-diff": "Ur tzemreḍ ara ad ẓṛeḍ \"diff\" agi acku yiwet n lqem-is <strong>tetwesfeḍ</strong>.\nTilɣa llant deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n usfeḍ].",
+       "rev-suppressed-no-diff": "Ur tzemreḍ ara ad ẓṛeḍ \"diff\" agi acku yiwet n lqem-is <strong>tetwekkes</strong>.",
+       "rev-deleted-unhide-diff": "Yiwen lqem n tameẓla agi <strong>yetwesfeḍ</strong>.\nTilɣa llant deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n usfeḍ].\nTzemreḍ meqqar [$1 ad ẓṛeḍ tameẓla agi] ma tebɣiḍ",
+       "rev-suppressed-unhide-diff": "Yiwen lqem n tameẓla agi <strong>yetwekkes</strong>.\nTilɣa llant deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n umḥu].\nTzemreḍ meqqar [$1 ad ẓṛeḍ tameẓla agi] ma tebɣiḍ",
+       "rev-deleted-diff-view": "Yiwen lqem n \"diff\" agi <strong>yetwekkes</strong>.\nTzemreḍ att ẓṛeḍ ; tilɣa llant deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n umḥu].",
+       "rev-suppressed-diff-view": "Yiwen lqem n \"diff\" agi <strong>yetwesfeḍ</strong>.\nTzemreḍ att ẓṛeḍ ; tilɣa llant deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n usfeḍ].",
+       "rev-delundel": "ssken/ffer",
+       "rev-showdeleted": "Ssken",
+       "revisiondelete": "Mḥu/kkes amḥay tisiwal",
+       "revdelete-nooldid-title": "Lqem asaḍas ur i ɣbel ara",
+       "revdelete-nooldid-text": "Ur textareḍ ara lqem nnican akken ad txedmeḍ tawuri fell-as.",
+       "revdelete-no-file": "Afaylu id ssefruḍ ur yella ara.",
+       "revdelete-show-file-confirm": "Tebɣriḍ ad mḥuḍ tacaggart n ufaylu « <nowiki>$1</nowiki> » n $2 af $3 ?",
+       "revdelete-show-file-submit": "Ih",
+       "logdelete-selected": "{{PLURAL:$1|Tamirt n uɣmis tettwafren|Isallen n uɣmis ttwafernen}}:",
+       "revdelete-confirm": "Sergeg ma tebɣiḍ ad xedmeḍ tigawt agi, fehmeḍ inalkamen, dɣa temtawiḍ s [[{{MediaWiki:Policy-url}}|ilugan]].",
+       "revdelete-suppress-text": "Ilaq tukksa att illi <strong>kan</strong> deg tijṛa agi:\n* Tilɣa ahat tinergamin\n* Tilɣa ur sɛant ara amkan d-agi\n*: <em>tansa, uḍḍun n tilifun, uḍḍun n taɣellist tamettit, …</em>",
+       "revdelete-legend": "Sbebd akref n tamuɣli",
+       "revdelete-hide-text": "Aḍris n tacaggart",
+       "revdelete-hide-image": "Ffer ayen yellan deg ufaylu",
+       "revdelete-hide-name": "Ffer iberdan d iɣewwaṛen",
+       "revdelete-hide-comment": "Beddel agzul",
+       "revdelete-hide-user": "Isem n umseqdac/Tansa IP n umaẓrag",
+       "revdelete-hide-restricted": "Mḥu isefka agi i inedbalen d yimdanen wiyaḍ",
+       "revdelete-radio-same": "(ur beddel ara)",
+       "revdelete-radio-set": "Udrig",
+       "revdelete-radio-unset": "Yeban",
+       "revdelete-suppress": "Kkes talɣut seg inedbalen d yimdanen wiyaḍ",
+       "revdelete-unsuppress": "Kkes icekkilen ɣef tisiwal i yuɣalen-d",
+       "revdelete-log": "Ayɣer:",
+       "revdelete-submit": "Snes {{PLURAL:$1|i tacaggart i tettwafren|i ticggarin i tettwafren}}",
+       "revdelete-success": "Asekkud n ileqman yemucce war uguren.",
+       "revdelete-failure": "Iẓṛi n lqem ur yemucceḍ ara:\n$1",
+       "logdelete-success": "Asekkud n tamirt yettuxdem.",
+       "logdelete-failure": "Iẓṛi n uɣmis ur yezmer ara ad yesbadu:\n$1",
+       "revdel-restore": "beddel timezrit",
+       "pagehist": "Amezruy n usebter",
+       "deletedhist": "Amezruy yemḥa",
+       "revdelete-hide-current": "Yella anezri imi nemḥa aferdis yezemzen ass n $1 af $2 : d lqem aneggaru.\nUr yezmer ara ad yemḥu.",
+       "revdelete-show-no-access": "Yella anezri imi n beqqeḍ aferdis yezemzen ass n $1 af $2 : yecreḍ am \"ukrif\".\nUr tesɛiḍ ara izerfan n wadduf.",
+       "revdelete-modify-no-access": "Yella anezri imi nebeddel aferdis yezemzen ass n $1 af $2 : yecreḍ am \"ukrif\".\nUr tesɛiḍ ara izerfan n wadduf.",
+       "revdelete-modify-missing": "Yella anezri imi nebeddel aferdis yesɛan ID $1 : Ulac-it deg taffa n isefka !",
+       "revdelete-no-change": "<strong>Ɣur-wet:</strong> Aferdis yezemzen ass n $1 af $2 yesɛa yakan iɣewwaren n iẓṛi i tebɣiḍ.",
+       "revdelete-concurrent-change": "Yella anezri imi nebeddel aferdis yezemzen ass n $1 af $2: aẓayeris yetwebeddel sɣur amḍan nniḍen mi tbeddeleḍ\nẒeṛ iɣmisen.",
+       "revdelete-only-restricted": "Yella anezri imi nemḥa asekcem yezemzen ass n $1 af $2 : ur tzemreḍ ara ad mḥuḍ iferdisen agi i inedbalen war ad fruḍ tixtiṛiyin nniḍen n umḥu.",
+       "revdelete-reason-dropdown": "Tiɣẓinin timiranin n umḥu :\n** Akukel n izerfan umeskar (copyright) ;\n** Iwenniten naɣ tilɣa n yiwen ur yezgan ara ;\n** Tilɣa i zemren ad rgemen.",
+       "revdelete-otherreason": "Taɣẓint nniḍen/taɣzint tamarnant:",
+       "revdelete-reasonotherlist": "Taɣẓint nniḍen",
+       "revdelete-edit-reasonlist": "Beddel tiɣẓinin n umḥu i-d-yettuɣalen",
+       "revdelete-offender": "Ameskar n tacaggart:",
+       "suppressionlog": "Aɣmis n isfaḍen",
+       "suppressionlogtext": "Ddaw-agi, umuɣ n tukksiwin d ikyafen yellan ɣef ugbur yeffren i inedbalen.\nẒeṛ [[Special:BlockList|umuɣ ikyafen]] i umuɣ n tiririyin d ikyafen yellan d imahlanen.",
+       "mergehistory": "Zdi amezruy n isebtar",
+       "mergehistory-header": "Asebtar agi aken yeǧǧ ad tesduklem ileqman n umezruy n usebtar unṣib γer usebtar amaynut.\nSenked d akken tamhelt agi ad eǧǧ amezruy n usebtar ad ikemmel.",
+       "mergehistory-box": "Zdi lqem n sin isebtar",
+       "mergehistory-from": "Azar n usebter:",
+       "mergehistory-into": "Aserken n usebter:",
+       "mergehistory-list": "Amezruy n ibeddilen i nezmer an zdi",
+       "mergehistory-merge": "Ileqman id iteddun n [[:$1]] zemren ad twasduklen d [[:$2]]. Seqdec tigejdit n tqeffalt ṛadyu iwakken ad tesdukleḍ ala ileqman yettwasnulfan seg tazwara armi d azmez yettwamlan. Ẓeṛ d akken aseqdec n iseγwan n tunigin ad iwennez tigejdit agi.",
+       "mergehistory-go": "Ẓeṛ ibeddilen i nezmer an zdi",
+       "mergehistory-submit": "Azday n ileqman",
+       "mergehistory-empty": "Ulac lqem i nezmer an zdi.",
+       "mergehistory-done": "$3 {{PLURAL:$3|lqem|ileqman}} n $1 {{PLURAL:$3|yezdukel|zdukelen}} deg [[:$2]].",
+       "mergehistory-fail": "Ulamek an zdukel imezruyen. Fru tikkelt nniḍen asebter d iɣewwaren is n uzmez.",
+       "mergehistory-fail-invalid-source": "Asebter aɣbalu d arameɣtu.",
+       "mergehistory-no-source": "Azar n usebter $1 ulac-it.",
+       "mergehistory-no-destination": "Aserken n usebter $1 ulac-it",
+       "mergehistory-invalid-source": "Azar n usebter ilaq ad i sɛu azwel i ɣbelen.",
+       "mergehistory-invalid-destination": "Aserken n usebter ilaq ad i sɛu azwel i ɣbelen.",
+       "mergehistory-autocomment": "[[:$1]] yezdukel s [[:$2]]",
+       "mergehistory-comment": "[[:$1]] yezdukel s [[:$2]]: $3",
+       "mergehistory-same-destination": "Asebter n azar d usebter n userken ur zemren ara ad illin d yiwen",
+       "mergehistory-reason": "Ayɣer:",
+       "mergelog": "Aɣmis n izdayen",
+       "revertmerge": "Fru",
+       "mergelogpagetext": "Attan tebdart n wesdukel umezruy usebtar deg win n usebtar nniḍen amaynut.",
+       "history-title": "Tiẓṛi tiss sint umezruy n \"$1\"",
+       "difference-title": "$1 : Tameẓla gar ileqman",
+       "difference-title-multipage": "Timeẓliwin gar isebtar \"$1\" d \"$2\"",
+       "difference-multipage": "(Tameẓla gar isebtar)",
+       "lineno": "Ajerriḍ $1:",
+       "compareselectedversions": "Ẓer imgerraden ger tisiwal i textareḍ",
+       "showhideselectedversions": "Ssken/Ffer ileqman i xtiṛen",
+       "editundo": "ssefsu",
+       "diff-empty": "(Ulac amgerrad)",
+       "diff-multi-sameuser": "({{PLURAL:$1|Yiwen n uceggir askudan sɣuṛ aseqdac iman-is  ur d-yettwasken ara|$1 N iceggiren iskudanen sɣuṛ aseqdac iman-is ur d-ttwaseknen ara}})",
+       "diff-multi-otherusers": "({{PLURAL:$1|Yiwen n uceggir agrawan|$1 n iceggiren igrawanen}} sɣur {{PLURAL:$2|yiwen n useqdac-nniḍen|$2 n iseqdacen}} ur {{PLURAL:$1|d-yettwasken ara|d-ttwaseknen ara}})",
+       "diff-multi-manyusers": "({{PLURAL:$1|Yiwen lqem agrawan|$1 ileqman igrawanen}} af {{PLURAL:$2|aseqdac|$2 iseqdacen}} {{PLURAL:$1|yeffer|ffren}})",
+       "difference-missing-revision": "{{PLURAL:$1|Yiwet tacaggart|$1 ticaggartin}} n tameẓla agi ($1) {{PLURAL:$2|ur tella ara (ulac)|ur llant ara (ulac)}}.\n\nAcku azday n tameẓla, ɣef wayen tsennedeḍ, d-aqbur. Asebter yemḥa.\nTzemreḍ ad affeḍ tilɣa deg [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} uɣmis n isebtar yekksen].",
+       "searchresults": "Igmad n unadi",
+       "searchresults-title": "Igmad n unadi i \"$1\"",
+       "titlematches": "Ayen yecban azwel n umegrad",
+       "textmatches": "Ayen yecban azwel n usebter",
+       "notextmatches": "ulac ayen yecban azwel n usebter",
+       "prevn": "{{PLURAL:$1|$1}} ssabeq",
+       "nextn": "{{PLURAL:$1|$1}} ameḍfir",
+       "prev-page": "Asebter yezrin",
+       "next-page": "Asebter d-iteddun",
+       "prevn-title": "$1 {{PLURAL:$1|agmud n uqbel|igmad n uqbel}}",
+       "nextn-title": "$1 {{PLURAL:$1|agmud n sakin|igmad n sakin}}",
+       "shown-title": "Beqqeḍ $1 {{PLURAL:$1|agmud|igmad}} s usebter",
+       "viewprevnext": "Ẓer ($1 {{int:pipe-separator}} $2) ($3).",
+       "searchmenu-exists": "<strong>Yella asebter s isem \"[[:$1]]\" deg wiki agi.</strong> {{PLURAL:$2|0=|See also the other search results found.}}",
+       "searchmenu-new": "<strong>Rnu asebter \"[[:$1]]\" ɣef uwiki-agi!</strong> {{PLURAL:$2|0=|Wali daɣen asebter yettwafen s unadi-ik.|Wali daɣen igmaḍ n unadi yettwafen.}}",
+       "searchprofile-articles": "Isebtar n ugbur",
+       "searchprofile-images": "Agetmedia",
+       "searchprofile-everything": "Akk",
+       "searchprofile-advanced": "Anadi anemhal",
+       "searchprofile-articles-tooltip": "Nadi deg $1",
+       "searchprofile-images-tooltip": "Nadi  ifuyla agetmedia",
+       "searchprofile-everything-tooltip": "Nadi deg akk usmel (ula deg isebtaren n umyannan)",
+       "searchprofile-advanced-tooltip": "Fren ideggen n isemawen i unadi",
+       "search-result-size": "$1 ({{PLURAL:$2|1 awal|$2 awalen}})",
+       "search-result-category-size": "$1 {{PLURAL:$1|amseqdac|imseqdacen}} $2 ({{PLURAL:$2|adu-taggayt|adu-tiggayin}}, $3 {{PLURAL:$3|afaylu|ifuyla}})",
+       "search-redirect": "(awelleh seg $1)",
+       "search-section": "(tagzemt $1)",
+       "search-category": "(tigezmi $1)",
+       "search-file-match": "(yzega i ugbur n ufaylu)",
+       "search-suggest": "D awal $1 i tnadiḍ ?",
+       "search-rewritten": "Igmaḍ yettwaseknen i $1. Anadi n $2 deg umḍiq-is.",
+       "search-interwiki-caption": "Igmaḍ n isenfaṛen atmaten",
+       "search-interwiki-default": "Igmaḍ si $1:",
+       "search-interwiki-more": "(ugar)",
+       "search-interwiki-more-results": "ugar n igmaḍ",
+       "search-relatedarticle": "Amassaɣ",
+       "searchrelated": "amassaɣ",
+       "searchall": "akk",
+       "showingresults": "Tamuli n {{PLURAL:$1|<strong>Yiwen</strong> wegmud|<strong>$1</strong> n yigmad}} seg  #<strong>$2</strong>.",
+       "search-showingresults": "{{PLURAL:$4|Agmuḍ <strong>$1</strong> si <strong>$3</strong>|Igmaḍ <strong>$1 - $2</strong> si <strong>$3</strong>}}",
+       "search-nonefound": "Ulac igmad i usuter agi.",
+       "search-nonefound-thiswiki": "ulac Agmuḍ l site",
+       "powersearch-legend": "Anadi amahlan",
+       "powersearch-ns": "Nadi deg tallunin n isemawen",
+       "powersearch-togglelabel": "Ɛellem:",
+       "powersearch-toggleall": "akk",
+       "powersearch-togglenone": "ulac",
+       "powersearch-remember": "Cfu ɣef ufran n yinadiyen d-iteddun",
+       "search-external": "Anadi yeffɣen",
+       "searchdisabled": "Anadi deg {{SITENAME}} yettwakkes. Tzemreḍ ad tnadiḍ s Google. Meɛna ur tettuḍ ara, tasmult n google taqdimt.",
+       "search-error": "Tella tuccḍa deg unadi n : $1",
+       "search-warning": "Alɣu yettwammel degu unadi n: $1",
+       "preferences": "Ismenyifen",
+       "mypreferences": "Ismenyifen",
+       "prefs-edits": "Amḍan n ibeddlilen :",
+       "prefsnologintext2": "Ttxilek(m) qqen aken ad snifleḍ ismenyifen inek(m).",
+       "prefs-skin": "Aglim",
+       "skin-preview": "azar-asekdan",
+       "datedefault": "Ur sɛiɣ ara asemyifi",
+       "prefs-labs": "Tiseɣnin « labs »",
+       "prefs-user-pages": "Isebtar n useqdac",
+       "prefs-personal": "Profile n wemseqdac",
+       "prefs-rc": "Ibeddilen imaynuten",
+       "prefs-watchlist": "Tabdart n uḍfaṛ",
+       "prefs-editwatchlist": "Ẓreg tabdart n uḍfaṛ",
+       "prefs-editwatchlist-label": "Ẓreg inekcam ɣef tedbart n uḍfaṛ:",
+       "prefs-editwatchlist-edit": "Wali sakin kkes izwal n tebdart-ik n uḍfaṛ",
+       "prefs-editwatchlist-raw": "Ẓreg tabdart n uḍfaṛ deg uskar arewway",
+       "prefs-editwatchlist-clear": "Sfeḍ tabdart-ik n uḍfaṛ",
+       "prefs-watchlist-days": "Amḍan n ussan i ubeqqeḍ deg umuɣ n uɛassi:",
+       "prefs-watchlist-days-max": "Afellay $1 {{PLURAL:$1|ass|ussan}}",
+       "prefs-watchlist-edits": "Amḍan afellay n ubeddel ara d-ibabnen deg tebdart n uḍfaṛ:",
+       "prefs-watchlist-edits-max": "Amḍan afellay : 1000",
+       "prefs-watchlist-token": "Tiddest  umuɣ n uɛassi:",
+       "prefs-misc": "Ismenyifien-nniḍen",
+       "prefs-resetpass": "Beddel awal n uɛaddi",
+       "prefs-changeemail": "Beddel neɣ kkes tansan n yimayl",
+       "prefs-setemail": "Sbadu yiwet tansa e-mail",
+       "prefs-email": "Tixtiṛiyin n tira",
+       "prefs-rendering": "Tummant",
+       "saveprefs": "Smekti",
+       "restoreprefs": "Err akkw iɣewwaren s lexṣas (deg akkw tigezmiwin)",
+       "prefs-editing": "Aglaf",
+       "searchresultshead": "Iruzzi",
+       "stub-threshold": "Talast i umasal n iseɣwan n isumar ($1):",
+       "stub-threshold-sample-link": "amedya",
+       "stub-threshold-disabled": "Yensa",
+       "recentchangesdays": "Amḍan n ussan an beqqeḍ deg ibeddilen ineggura.",
+       "recentchangesdays-max": "Afellay $1 {{PLURAL:$1|ass|ussan}}",
+       "recentchangescount": "Amḍan n ibeddilen i ubeqqeḍ s lexṣas:",
+       "prefs-help-recentchangescount": "Wagi yesɛa deg-es ibeddilen ineggura, isebtar n umezruy d iɣmisen.",
+       "prefs-help-watchlist-token2": "Hattan tasarut tufurt n usuddem Web n umuɣ inek/inem n uḍfar.\nAkkw amḍan yesɛan tasarut agi, ad yezmer ad i ɣer umuɣ inek/inem n uḍfar, ur d-sselɣu ara tasarut agi ihi.\n[[Special:ResetTokens|Nqer d-agi ma tebɣiḍ ad wennezeḍ tasarut agi]].",
+       "savedprefs": "Ismenyifen-ik ttwaskelsen.",
+       "savedrights": "Izerfan n useqdac n {{GENDER:$1|$1}}  ttwaskelsen.",
+       "timezonelegend": "Iẓḍi n ukud:",
+       "localtime": "Asrag adigan :",
+       "timezoneuseserverdefault": "Seqdec azal s lexṣas n wiki ($1)",
+       "timezoneuseoffset": "Nniḍen (ssefru asekḥer)",
+       "servertime": "Asrag n uqeddac:",
+       "guesstimezone": "Sseqdec azal n iminig",
+       "timezoneregion-africa": "Tafriqt",
+       "timezoneregion-america": "Tamrikt",
+       "timezoneregion-antarctica": "Antarktik",
+       "timezoneregion-arctic": "Arktik",
+       "timezoneregion-asia": "Asya",
+       "timezoneregion-atlantic": "Agaraw At'lasi",
+       "timezoneregion-australia": "Usṭralya",
+       "timezoneregion-europe": "Turuft",
+       "timezoneregion-indian": "Agaraw Ahendi",
+       "timezoneregion-pacific": "Agaraw Amelwi",
+       "allowemail": "Eǧǧ imseqdacen wiyaḍ a k-aznen email",
+       "prefs-searchoptions": "Iruzzi",
+       "prefs-namespaces": "Talluntin n isemawen",
+       "default": "ameslugen",
+       "prefs-files": "ifuyla",
+       "prefs-custom-css": "CSS asagen",
+       "prefs-custom-json": "JSON asagen",
+       "prefs-custom-js": "JavaScript asagen",
+       "prefs-common-config": "JavaScript  d CSS azduklan i akkw lebsa:",
+       "prefs-reset-intro": "Tzemreḍ ad seqdeceḍ asebter agi iwakken ad erreḍ iɣewwaren inek/inem ar azalen n lexṣas n usmel.\nWagi ur yezmer ara ad yetwekkes.",
+       "prefs-emailconfirm-label": "Aragag n tirawt:",
+       "youremail": "E-mail:",
+       "username": "{{GENDER:$1|Isem n umseqdac|Isem n tamseqdact}}:",
+       "prefs-memberingroups": "{{GENDER:$2|Aεeggal|Taɛggalt}} n {{PLURAL:$1|ugraw|igrawen}}:",
+       "group-membership-link-with-expiry": "$1 (arams d $2)",
+       "prefs-registration": "Azmez n tiggezt:",
+       "yourrealname": "Isem n ṣṣeḥ:",
+       "yourlanguage": "Tutlayt:",
+       "yourvariant": "Lqem nniḍen n tutlayt n ugbur:",
+       "prefs-help-variant": "Lqem naɣ inun inek/inem iwakken an beqqeḍ agbur n wiki agi.",
+       "yournick": "Azmul amaynut:",
+       "prefs-help-signature": "Iwenniten ɣef isebtar n umeslay ilaq ad illin zmelen s « <nowiki>~~~~</nowiki> », sakin ad i sɛu aselkat ɣer azmul inek/inem dɣa azmez d usrag.",
+       "badsig": "Azmul mačči d ṣaḥiḥ; \nSsenqed tags n HTML.",
+       "badsiglength": "Azmul inek/inem, teɣwzi-s tameqqṛant aṭas.\nUr ilaq ara ad i sɛu ugar n $1 {{PLURAL:$1|asekkil|isekkilen}}.",
+       "yourgender": "Amek i tebɣiḍ ad n-ini fellak(m) ?",
+       "gender-unknown": "Ur bɣiɣ ara ad iniɣ",
+       "gender-male": "Yebeddel isebtar n wiki",
+       "gender-female": "Tebeddel isebtar n wiki",
+       "prefs-help-gender": "Sbadu asmenyif agi d-afrayan.\nAseɣẓan agi yetseqdac azal-is iwakken ad yemeslay s kečč/kem dɣa ad yefk isem-ik/im i wiyaḍ nniḍen s useqdac n tawsit tajeṛṛumant.\nTalɣut agi attili d-tazayezt.",
+       "email": "E-mail",
+       "prefs-help-realname": "Isem n tidet d anufran.\nma tefkeḍ-t-id, ad yettuseqdac iwaken ad ak(m)-d ttwanefkent tebzirin inek(m).",
+       "prefs-help-email": "E-mail (am tebɣiḍ): Teǧǧi imseqdacen wiyaḍ a k-aznen email mebla ma ẓren tansa email inek.",
+       "prefs-help-email-others": "Zemreḍ ad eǧǧeḍ wiyeḍ nniḍen ak(akem) cceqɛen izen deg usebter-ik (im) n umyannan war ad effekeḍ tamagit-ik (im).",
+       "prefs-help-email-required": "Tansa e-mail tesḍulli.",
+       "prefs-info": "Tilɣa n udasil",
+       "prefs-i18n": "Asagraɣlan",
+       "prefs-signature": "Azmul",
+       "prefs-dateformat": "Amasal n izemzan",
+       "prefs-timeoffset": "Asekḥer n usrag",
+       "prefs-advancedediting": "Tixtiṛiyin timuta",
+       "prefs-editor": "Amaẓrag",
+       "prefs-preview": "azar-asekdan",
+       "prefs-advancedrc": "Tixtiṛiyin timahlanin",
+       "prefs-advancedrendering": "Tixtiṛiyin timahlanin",
+       "prefs-advancedsearchoptions": "Tixtiṛiyin timahlanin",
+       "prefs-advancedwatchlist": "Tixtiṛiyin timahlanin",
+       "prefs-displayrc": "Tixtiṛiyin n ubeqqeḍ",
+       "prefs-displaywatchlist": "Tixtiṛiyin n ubeqqeḍ",
+       "prefs-tokenwatchlist": "Tiddest",
+       "prefs-diffs": "Timeẓliwin",
+       "prefs-help-prefershttps": "Asmenyif agi, ad yelḥu ar tuqqna ay d-yetteddun.",
+       "prefs-tabs-navigation-hint": "Taxbalut: Tzemreḍ ad seqdeceḍ tineccabin n uzelmaḍ d uyeffus iwakken ad ssileleḍ gar iccaren.",
+       "userrights": "Izerfan n useqdac",
+       "userrights-lookup-user": "Fren aseqdac",
+       "userrights-user-editname": "Sekcem isem n useqdac",
+       "editusergroup": "Sali-d igrawen n iseqdacen",
+       "editinguser": "Abeddel n izerfan n {{GENDER:$1|useqdac|tseqdact}} <strong>[[User:$1|$1]]</strong> $2",
+       "viewinguserrights": "Askan n izefan iseqdacen n {{GENDER:$1|useqdac|tseqdact}} <strong>[[User:$1|$1]]</strong> $2",
+       "userrights-editusergroup": "Snifel izerfan n {{GENDER:$1|useqdsac|tseqdact}}",
+       "userrights-viewusergroup": "Sken igrawen n {{GENDER:$1|useqdac|tseqdact}}",
+       "saveusergroups": "Sekles igrawen n {{GENDER:$1|useqdac|tseqdact}}",
+       "userrights-groupsmember": "Amaslad deg:",
+       "userrights-groupsmember-auto": "Aεeggal udrig n:",
+       "userrights-groups-help": "Tzemreḍ ad beddeleḍ igrawen anda yella aseqdac agi :\n* Taxxamt i tekkin : aseqdac yella deg ugraw agi.\n* Taxxamt ur tekkin ara : aseqdac ur yella ara deg ugraw agi\n* Titrit (*) : ur tzemreḍ ara ad ekkeseḍ agraw agi sakin i tid ernuḍ, naɣ bis-bersa.",
+       "userrights-reason": "Ayɣer:",
+       "userrights-no-interwiki": "Ur tesɛiḍ ara turagt iwakken ad beddeleḍ izerfan n iseqdacen ɣef wiki nniḍen.",
+       "userrights-nodatabase": "Taffa n isefka $1 ulac itt naɣ mačči d tadigant.",
+       "userrights-changeable-col": "Igrawen i tzemreḍ ad beddeleḍ",
+       "userrights-unchangeable-col": "Igrawen ur tzemreḍ ara ad beddeleḍ",
+       "userrights-expiry-current": "Ad ifat di $1",
+       "userrights-expiry-none": "Ur yettfat ara",
+       "userrights-expiry": "Ad ifat di:",
+       "userrights-expiry-existing": "Azemz n ufati yellan: $3,  $2",
+       "userrights-expiry-othertime": "Akud-nniḍen:",
+       "userrights-expiry-options": "1 ass:1 day,1 amalas:1 week,1 aggur:1 month,3 agguren:3 montghs,6 agguren:6 month,1 aseggas:1 year",
+       "userrights-invalid-expiry": "Azemz n tagara i ugraw \"$1\" mačči d ameɣtu.",
+       "userrights-expiry-in-past": "Azemz n tagara i ugraw \"$1\" yezri.",
+       "userrights-conflict": "Ccwal n ubeddel n izerfan n umseqdac ! Ilaq ad ɛzemeḍ tikelt nniḍen dɣa ad sergegeḍ ibeddilen.",
+       "group": "Adrum:",
+       "group-user": "Iseqdacen",
+       "group-autoconfirmed": "Iseqdacen i rgegen",
+       "group-bot": "Iṛubuten",
+       "group-sysop": "Inedbalen",
+       "group-bureaucrat": "Imsifellura",
+       "group-suppress": "Inemdayen",
+       "group-all": "(akk)",
+       "group-user-member": "{{GENDER:$1|aseqdac|taseqdact}}",
+       "group-autoconfirmed-member": "{{GENDER:$1|manrgeg aseqdac|manrgeg taseqdact}}",
+       "group-bot-member": "{{GENDER:$1|aṛubut}}",
+       "group-sysop-member": "{{GENDER:$1|anedbal|tanedbalt}}",
+       "group-bureaucrat-member": "{{GENDER:$1|amsfellaru}}",
+       "group-suppress-member": "{{GENDER:$1|anemday|tanemdayt}}",
+       "grouppage-user": "{{ns:project}}:Iseqdacen",
+       "grouppage-autoconfirmed": "{{ns:project}}:Iseqdacen i rgegen",
+       "grouppage-bot": "{{ns:project}}:Iṛubuten",
+       "grouppage-sysop": "{{ns:project}}:Inedbalen",
+       "grouppage-bureaucrat": "{{ns:project}}:Imsfelluran",
+       "grouppage-suppress": "{{ns:project}}:Suppress",
+       "right-read": "Ɣeṛ isebtar",
+       "right-edit": "Beddel isebtar",
+       "right-createpage": "Snulfud isebtar (mačči d-isebtar n umeslay)",
+       "right-createtalk": "Snulfud isebtar n umeslay",
+       "right-createaccount": "Snulfud imiḍanen n iseqdacen",
+       "right-autocreateaccount": "Tuqqna tawurmant s umiḍan n useqdac azɣaray",
+       "right-minoredit": "Ffer ibeddilen yellan d-imectuḥen",
+       "right-move": "Beddel isem n isebtar",
+       "right-move-subpages": "Beddel isem n isebtar d adu-isebtar nsen",
+       "right-move-rootuserpages": "Beddel isem n usebtar amenzawi n useqdac",
+       "right-move-categorypages": "Ugar n isebtar n taggayin",
+       "right-movefile": "Beddel isem n ifuyla",
+       "right-suppressredirect": "Ur snulfu ara asemmimeḍ seg azwel amezwaru s ubeddel n isem usebter",
+       "right-upload": "Azen ifuyla",
+       "right-reupload": "Sefxes afaylu yellan",
+       "right-reupload-own": "Sefxes afaylu id n-azen.",
+       "right-reupload-shared": "Ɛefes deg udigan afaylu yellan ɣef azadur azduklan",
+       "right-upload_by_url": "Kter afaylu seg tansa URL",
+       "enhancedrc-history": "amazray",
+       "recentchanges-submit": "Ssken",
+       "rcshowhideminor-show": "Ssken",
+       "rcshowhideminor-hide": "Ffer",
+       "rcshowhidebots": "$1 Iṛubuten",
+       "rcshowhidebots-show": "Ssken",
+       "rcshowhidebots-hide": "Ffer",
+       "rcshowhideliu-show": "Ssken",
+       "rcshowhideliu-hide": "Ffer",
+       "rcshowhideanons-show": "Ssken",
+       "rcshowhideanons-hide": "Ffer",
+       "rcshowhidepatr-show": "Ssken",
+       "rcshowhidemine-show": "Ssken",
+       "rcshowhidemine-hide": "Ffer",
+       "rcshowhidecategorization-show": "Ssken",
+       "hide": "Ffer",
+       "show": "Ssken",
+       "upload": "Azen afaylu",
+       "imgfile": "Afaylu",
+       "file-anchor-link": "Afaylu",
+       "withoutinterwiki-submit": "Ssken",
+       "nbytes": "$1 {{PLURAL:$1|byte|bytes}}",
+       "prefixindex-submit": "Ssken",
+       "newpages-submit": "Ssken",
+       "newpages-username": "Isem n useqdac:",
+       "logeventslist-submit": "Ssken",
+       "categories-submit": "Ssken",
+       "linksearch-ok": "Iruzzi",
+       "listusers-submit": "Ssken",
+       "emailusername": "Isem n useqdac:",
+       "watchlist-submit": "Ssken",
+       "historyaction-submit": "Ssken",
+       "restriction-edit": "Glef",
+       "undelete-search-submit": "Iruzzi",
+       "namespace": "Talluntin n isemawen",
+       "sp-contributions-submit": "Iruzzi",
+       "ipblocklist-submit": "Iruzzi",
+       "tooltip-pt-logout": "Ffeɣ",
+       "tooltip-search": "Iruzzi {{SITENAME}}",
+       "tooltip-t-upload": "Azen ifuyla",
+       "tooltip-ca-nstab-category": "Ẓer asebter n taggayin",
+       "pageinfo-toolboxlink": "Tilɣa n udasil",
+       "pageinfo-contentpage-yes": "Ih",
+       "show-big-image-size": "$1 × $2 pixels",
+       "ilsubmit": "Iruzzi",
+       "days": "{{PLURAL:$1|$1 ass|$1 ussan}}",
+       "namespacesall": "akk",
+       "monthsall": "akk",
+       "fileduplicatesearch-submit": "Iruzzi",
+       "specialpages": "Asebter uslig",
+       "tags-active-yes": "Ih",
+       "tags-active-no": "Uhu",
+       "searchsuggest-search": "Iruzzi {{SITENAME}}",
+       "duration-days": "$1 {{PLURAL:$1|ass|ussan}}"
+}
index 781de09..0ee213e 100644 (file)
        "viewtalkpage": "مباحثہ ݙیکھو",
        "otherlanguages": "ٻنھاں زباناں وچ",
        "redirectedfrom": "($1 کنوں ولدا رجوع )",
-       "redirectpagesub": "صفحہ ریڈائریکٹ کرو",
+       "redirectpagesub": "ورقہ ریڈائریکٹ کرو",
        "redirectto": "اڳے کرو:",
        "lastmodifiedat": "ایہ ورقہ چھیکڑی واری  $1 کوں $2 تے تبدیل تھیا ہائی۔",
        "protectedpage": "آم شام ورقہ",
        "whatlinkshere-page": "ورقہ",
        "linkshere": "<strong>[[:$1]]</strong> نال درج ذیل ورقے مربوط ہن:",
        "nolinkshere": "<strong>[[:$1]]</strong> نال کوئی ورقہ مربوط کائنی۔",
-       "isredirect": "صفحہ ریڈائریکٹ کرو",
+       "isredirect": "ورقہ ریڈائریکٹ کرو",
        "istemplate": "شامل شدہ",
        "isimage": "فائل دا ربط",
        "whatlinkshere-prev": "{{PLURAL:$1|پچھلا|پچھلے $1}}",
        "pageinfo-header-basic": "بنیادی معلومات",
        "pageinfo-header-edits": "تاریخچۂ ترمیم",
        "pageinfo-header-restrictions": "ورقے دی حفاظت",
-       "pageinfo-header-properties": "صفحہ دی خاصیتاں",
+       "pageinfo-header-properties": "ورقے دیاں خاصیتاں",
        "pageinfo-display-title": "عنوان",
        "pageinfo-default-sort": "کلید برائے ابتدائی ترتیب",
        "pageinfo-length": "ورقے دی لمباݨ (بائٹ وچ)",
        "pageinfo-few-watchers": "$1 کنوں گھٹ {{PLURAL:$1|ناظر|ناظرین}}",
        "pageinfo-redirects-name": "رجوعاں  دی تعداد",
        "pageinfo-subpages-name": "ایں ورقے دے ذیلی ورقیاں دی تعداد",
-       "pageinfo-firstuser": "صفحہ ساز",
-       "pageinfo-firsttime": "صفحہ سازی دی تاریخ",
+       "pageinfo-firstuser": "ورقہ ساز",
+       "pageinfo-firsttime": "ورقہ بݨݨ دی تاریخ",
        "pageinfo-lastuser": "چھیکڑی ترمیم کنندہ",
        "pageinfo-lasttime": "چھیکڑی ترمیم دی تاریخ",
        "pageinfo-edits": "ترامیم دی مجموعی تعداد",
        "pageinfo-magic-words": "جادوئی {{PLURAL:$1|لفظ|الفاظ}} ($1)",
        "pageinfo-hidden-categories": "پوشیدہ {{PLURAL:$1|زمرہ|زمرہ جات}} ($1)",
        "pageinfo-templates": "زیر استعمال {{PLURAL:$1|سانچہ|سانچے}} ($1)",
-       "pageinfo-toolboxlink": "معلومات صفحہ",
+       "pageinfo-toolboxlink": "معلومات ورقہ",
        "pageinfo-contentpage": "شمار بطور ورقہ",
        "pageinfo-contentpage-yes": "ڄیا",
        "patrol-log-page": "گشت لاگ",
        "logentry-upload-overwrite": "$1 نے $3 دا نواں نسخہ {{GENDER:$2|اپلوڈ کیتا}}",
        "searchsuggest-search": "ڳولو",
        "duration-days": "$1 {{PLURAL:$1|ݙینہ}}",
-       "randomrootpage": "بے ترتيب بنیادی صفحہ"
+       "randomrootpage": "بے ترتيب بنیادی ورقہ"
 }
index 8e3c459..2d87bfb 100644 (file)
        "recentchangeslinked-feed": "Sorodne spremembe",
        "recentchangeslinked-toolbox": "Sorodne spremembe",
        "recentchangeslinked-title": "Spremembe, povezane z \"$1\"",
-       "recentchangeslinked-summary": "Vnesite ime strani, da vidite spremembe strani, povezanih na ali s te strani. (Da vidite člane kategorije, vnesite Kategorija:Ime kategorije.)\nStrani z [[Special:Watchlist|vašega spiska nadzorov]] so <strong>odebeljene</strong>.",
+       "recentchangeslinked-summary": "Vnesite ime strani, da vidite spremembe strani, povezanih na ali s te strani. (Da vidite člane kategorije, vnesite {{ns:category}}:Ime kategorije.)\nStrani z [[Special:Watchlist|vašega spiska nadzorov]] so <strong>odebeljene</strong>.",
        "recentchangeslinked-page": "Naslov strani:",
        "recentchangeslinked-to": "Prikaži spremembe na določeno stran povezanih strani",
        "recentchanges-page-added-to-category": "[[:$1]] dodano v kategorijo",
index 5dfc2db..c0e0968 100644 (file)
        "rcfilters-filter-watchlistactivity-unseen-description": "Измене страница које нисте посетили од када су направљене измене.",
        "rcfilters-filter-watchlistactivity-seen-label": "Погледане измене",
        "rcfilters-filter-watchlistactivity-seen-description": "Измене страница које сте посетили од када су направљене измене.",
-       "rcfilters-filtergroup-changetype": "Ð\92Ñ\80Ñ\81Ñ\82а измене",
+       "rcfilters-filtergroup-changetype": "Тип измене",
        "rcfilters-filter-pageedits-label": "Измене страница",
        "rcfilters-filter-pageedits-description": "Измене вики садржаја, расправа, описа категорија…",
-       "rcfilters-filter-newpages-label": "СÑ\82ваÑ\80ање страница",
+       "rcfilters-filter-newpages-label": "Ð\9fÑ\80авÑ\99ење страница",
        "rcfilters-filter-newpages-description": "Измене којима се стварају нове странице.",
        "rcfilters-filter-categorization-label": "Измене категорија",
        "rcfilters-filter-categorization-description": "Записи о страницама додатим или уклоњеним из категорија.",
index 7bcf29d..4a42045 100644 (file)
        "prefs-dateformat": "Datumformat",
        "prefs-timeoffset": "Tidsförskjutning",
        "prefs-advancedediting": "Allmänna alternativ",
+       "prefs-developertools": "Utvecklarverktyg",
        "prefs-editor": "Redigerare",
        "prefs-preview": "Förhandsvisa",
        "prefs-advancedrc": "Avancerade alternativ",
index 575466f..d78fcd1 100644 (file)
        "laggedslavemode": "Dej pozůr: Ta zajta może ńy mjeć nojnowszych aktualizacyjůw.",
        "readonly": "Baza danych je zawarto",
        "enterlockreason": "Naszkryflej sam powůd zawarćo bazy danych a za wjela (myńi-wjyncyj) ja uodymkńesz",
-       "readonlytext": "Baza danych jest terozki zawarto\n- ńy do śe wćepywać nowych artikli ńi sprowjać już wćepanych. Powodym\nsům prawdopodańy czynnośći admińistracyjne. Po jejich zakůńczyńu cołko funkcjonalność bazy bydźe prziwrůcono.\nAdministrator, kery zablokowoł baza, podoł takie wyjaśńyńy:<br /> $1",
+       "readonlytext": "Baza danych je terŏzki zawartŏ na nowe informacyje i pōmiynianie przōdzij wkludzōnych. Je to nojpewnij skuli czynności administracyjnych. Po jejich skōńczyniu baza bydzie nazŏd fungować.\nAdministratōr, kery zablokowoł bazã, doł takie tuplikowaniy:<br /> $1",
        "missing-article": "W databaźe ńy idzie nolyźć treść zajty „$1” $2.\n\nUobwykle je to spůsobiůne tym, że sznupoł żeś po ńyaktualnym linku na zmjyny mjyndzy půmjyńańami, abo do wyćepanyj wersyje z gyszichty sprowjyń zajty.\n\nEli tak ńy je, możno śe trefił feler we softwaru MediaWiki. Kej ja, pedz uo tym [[Special:ListUsers/sysop|admińistratorowi]] a podej mu adres URL.",
        "missingarticle-rev": "(wersyjo#: $1)",
        "missingarticle-diff": "(dyferencyjo: $1, $2)",
        "viewyourtext": "We tekst zdrzůduowy tyj zajty możno dali filować, idźe go tyż kopjować.",
        "protectedinterface": "Na tyj zajće znojduje śe tekst interfejsu uoprogramowańo, bestůż uůna je zawarto uod sprowjańo. Coby doćepnůńć abo sprowjić tůmaczyńa wszyskich serwerůw, użyj [https://translatewiki.net/ translatewiki.net], průjyktu lokalizacyji MediaWiki.",
        "editinginterface": "''''Dej pozůr:''' Sprowjosz zajta, na keryj je tekst interfejsu uoprogramowańo. Pomjyńyńa na tyj zajće zmjyńům wyglůnd interfejsu lo inkszych użytkowńikůw. Coby doćepnůńć abo sprowjić tůmaczyńa, użyj [https://translatewiki.net/wiki/Main_Page?setlang=szl translatewiki.net].",
-       "cascadeprotected": "Ta zajta je zawarto uod sprowjańo, po takymu, co uůna je załůnczůno na {{PLURAL:$1|nastympujůncyj zajće, kero zostoła zawarto|nastympujůncych zajtach, kere zostoły zawarte}} ze załůnczůnům uopcyjům dźedźiczyńo:\n$2",
+       "cascadeprotected": "Ta zajta je chrōniōnŏ ôd edycyje, skuli tego co je ôna wkludzōnŏ do {{PLURAL:$1|nastympujōncyj zajty, kerŏ ôstała ôchrōniōnŏ|nastympujōncych zajtach, kere ôstały ôchrōniōne}} ze załōnczōnōm ôpcyjōm erbowaniŏ:\n$2",
        "namespaceprotected": "Ńy mosz uprowńyń, coby sprowjać zajty we raumje mjan '''$1'''.",
        "customcssprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty, bo na ńij sům uosobiste sztalowańo inkszego użytkowńika.",
        "customjsprotected": "Ńy mosz uprawńyń do sprowjańo tyj zajty, bo na ńij sům uosobiste sztalowańo inkszego użytkowńika.",
        "noname": "To ńy je poprowne mjano użytkowńika.",
        "loginsuccesstitle": "Logowańy udane",
        "loginsuccess": "'''Terozki jeżeś zalogowany do {{SITENAME}} kej \"$1\".'''",
-       "nosuchuser": "Ńy ma sam użytkowńika uo mjańe \"$1\".\nSprowdź szrajbůng, abo [[Special:CreateAccount|utwůrz nowe kůnto]].",
+       "nosuchuser": "Niy ma używŏcza ô mianie \"$1\".\nBadnij szrajbōng, abo [[Special:CreateAccount|sprŏw nowe kōnto]].",
        "nosuchusershort": "Ńy mo sam użytkowńika uo mjańe \"$1\".",
        "nouserspecified": "Podej mjano użytkowńika.",
        "login-userblocked": "Tyn sprowjorz mo zawarte sprowjyńa. Ńy idźe śe zalogować.",
index 0ea7b52..69121fa 100644 (file)
        "savechanges": "மாற்றங்களைச் சேமி",
        "publishpage": "பக்கத்தைப் பதிப்பிடுக",
        "publishchanges": "மாற்றங்களைப் பதிப்பிடுக",
+       "publishchanges-start": "மாற்றங்களைப் பதிப்பிடுக…",
        "preview": "முன்தோற்றம்",
        "showpreview": "முன்தோற்றம் காட்டு",
        "showdiff": "மாற்றங்களைக் காட்டு",
index f93d60e..e73941a 100644 (file)
@@ -15,7 +15,8 @@
                        "AryanSogd",
                        "ToJack",
                        "Vashgird",
-                       "Fitoschido"
+                       "Fitoschido",
+                       "TajikMaterialist"
                ]
        },
        "tog-underline": "Пайвандҳо хаткашида:",
@@ -79,7 +80,7 @@
        "fri": "Ҷу",
        "sat": "Шн",
        "january": "январ",
-       "february": "феврал",
+       "february": "Феврал",
        "march": "март",
        "april": "апрел",
        "may_long": "май",
index a5c8ec0..c15d558 100644 (file)
        "action-edit": "бу битне үзгәртү",
        "action-createpage": "бу битне төзү",
        "action-createtalk": "бу бәхәс битен төзү",
+       "action-createaccount": "бу кулланучы язмасын ясау",
        "action-move": "бу битне күчерергә",
        "action-sendemail": "электрон хат җибәрү",
        "nchanges": "$1 {{PLURAL:$1|үзгәртү}}",
        "newsectionsummary": "/* $1 */ яңа бүлек",
        "rc-enhanced-expand": "Ваклыкларны күрсәтү",
        "rc-enhanced-hide": "Ваклыкларны яшерү",
+       "rc-old-title": "башта «$1» буларак ясала",
        "recentchangeslinked": "Бәйләнешле үзгәртүләр",
        "recentchangeslinked-feed": "Бәйләнешле үзгәртүләр",
        "recentchangeslinked-toolbox": "Бәйләнешле үзгәртүләр",
        "unwatch": "Күзәтмәү",
        "unwatchthispage": "Күзәтүне туктат",
        "notanarticle": "Мәкалә түгел",
-       "watchlist-details": "Күзәтү исемлегегездә, бәхәс битләрен санамыйча, {{PLURAL:$1|$1 бит}} бар.",
+       "watchlist-details": "Күзәтү исемлегегездә (бәхәс битләре белән бергә) {{PLURAL:$1|$1 бит}}.",
        "wlheader-enotif": "Электрон почта аша белдерүләр ачык.",
        "wlheader-showupdated": "Сезнең соңгы төзәтмәләрдән соң үзгәргән битләр <strong>калын</strong> шрифт белән күрсәтелгән.",
        "wlnote": "Түбәндә $3 $4 вакыт аралыгының {{PLURAL:$2|соңгы сәгатендә|соңгы <strong>$2</strong> сәгатендә}} ясалган {{PLURAL:$1|ахыргы төзәтмә|ахыргы <strong>$1</strong> төзәтмә}} күрсәтелгән.",
        "mycontris": "Кертем",
        "anoncontribs": "Кертем",
        "contribsub2": "Кертем {{GENDER:$3|$1}} ($2)",
+       "nocontribs": "Күрсштелгән шартларга җавап биргән үзгәрешләр таҗылмады.",
        "uctop": "(хәзерге)",
        "month": "Айдан башлап (һәм элегрәк):",
        "year": "Елдан башлап (һәм элегрәк):",
        "emaillink": "хат язу",
        "blocklogpage": "Тыю көндәлеге",
        "blocklogentry": "[[$1]] $2 вакытка тыелды $3",
+       "reblock-logentry": "[[$1]] тыю көләүләрен $2 $3 вакыт арасына үзгәртте",
        "unblocklogentry": "$1 кулланучысының тыелу вакыты бетте",
        "block-log-flags-anononly": "аноним кулланучылар гына",
        "block-log-flags-nocreate": "яңа хисап язмасы теркәү тыелган",
index e9fb2bb..7f10673 100644 (file)
        "rcfilters-target-page-placeholder": "ایک صفحہ کا نام (یا زمرہ) درج کریں",
        "rcnotefrom": "ذیل میں <strong>$2</strong> سے کی گئی {{PLURAL:$5|تبدیلی|تبدیلیاں}} <strong>$1</strong> تک دکھائی جا رہی ہیں۔",
        "rclistfromreset": "انتخاب تاریخ کی ترتیب نو",
-       "rclistfrom": "$2، $3ء سے ہونے والی نئی تبدیلیاں دکھائیں",
+       "rclistfrom": "$2، $3 سے ہونے والی نئی تبدیلیاں دکھائیں",
        "rcshowhideminor": "معمولی ترامیم $1",
        "rcshowhideminor-show": "دکھائیں",
        "rcshowhideminor-hide": "چھپائیں",
index 73a9b83..aa78905 100644 (file)
        "savechanges": "Lưu các thay đổi",
        "publishpage": "Đăng trang",
        "publishchanges": "Đăng thay đổi",
+       "savearticle-start": "Lưu trang...",
+       "savechanges-start": "Lưu thay đổi...",
+       "publishpage-start": "Đăng trang...",
+       "publishchanges-start": "Đăng thay đổi...",
        "preview": "Xem trước",
        "showpreview": "Xem trước",
        "showdiff": "Xem thay đổi",
index 00f4e0f..cc2c27e 100644 (file)
@@ -17,6 +17,7 @@
        "tog-ccmeonemails": "ⴰⵣⵏ ⵉⵢⵉ ⴷ ⵜⵓⵏⵖⵉⵍⵉⵏ ⵏ ⵉⵎⴰⵢⵍⵏ ⵏⵏⴰ ⵓⵣⵏⵖ ⵉ ⵉⵎⵙⵙⵎⵔⵙⵏ ⵢⴰⴹⵏ",
        "tog-diffonly": "ⴰⴷ ⵓⵔ ⵜⵙⵙⴽⴷ ⵜⵓⵎⴰⵢⵜ ⵏ ⵜⴰⵙⵏⴰ ⴷⴷⵓ ⵉⵎⵣⴰⵔⴰⵢⵏ",
        "tog-showhiddencats": "ⵙⴽⵏ ⵜⴰⴳⴳⴰⵢⵉⵏ ⵉⵜⵜⵓⵃⴹⴰⵏ",
+       "underline-never": "ⵓⵙⴰⵔ",
        "sunday": "ⴰⵙⴰⵎⴰⵙ",
        "monday": "ⴰⵢⵏⴰⵙ",
        "tuesday": "ⴰⵙⵉⵏⴰⵙ",
        "privacypage": "Project:ⵜⴰⵙⵔⵜⵉⵜ ⵏ ⵜⵉⵏⵏⵓⵜⵍⴰ",
        "ok": "ⵡⴰⵅⵅⴰ",
        "retrievedfrom": "ⵉⵜⵜⵓⵙⴰⵖⵓⵍ ⵙⴳ $1",
+       "youhavenewmessages": "{{PLURAL:$3|ⵖⵓⵔⴽ}} $1 ($2).",
        "youhavenewmessagesmanyusers": "ⴷⴰⵔⴽ $1 ⵙⴳ ⵎⵏⵏⴰⵡ ⵉⵎⵙⵙⵎⵔⵙⵏ ($2)",
        "newmessageslinkplural": "{{PLURAL:$1|ⵜⵓⵣⵉⵏⵜ ⵜⴰⵎⴰⵢⵏⵓⵜ|999=ⵜⵓⵣⵉⵏⵉⵏ ⵜⵉⵎⴰⵢⵏⵓⵜⵉⵏ}}",
        "youhavenewmessagesmulti": "ⵍⵍⴰⵏ ⵖⵓⵔⴽ ⵜⵓⵣⵉⵏⵉⵏ ⵜⵉⵎⴰⵢⵏⵓⵜⵉⵏ ⴳ $1",
        "nstab-help": "ⵜⴰⵙⵏⴰ ⵏ ⵜⵡⵉⵙⵉ",
        "nstab-category": "ⴰⵙⵎⵉⵍ",
        "mainpage-nstab": "ⵜⴰⵙⵏⴰ ⵏ ⵓⵙⵏⵓⴱⴳ",
+       "nosuchspecialpage": "ⴰⵡⴷ ⵢⴰⵜ ⵜⴰⵙⵏⴰ ⵉⵥⵍⵉⵏ.",
+       "nospecialpagetext": "<strong>ⵜⴻⵜⵜⵔⴷ ⵢⴰⵜ ⵜⴰⵙⵏⴰ ⵉⵥⵍⵉⵏ ⵓⵔ ⵉⵅⴷⵉⵎⵏ</strong>",
        "error": "ⵜⴰⵣⴳⵍⵜ",
        "databaseerror-error": "ⵜⴰⵣⴳⵍⵜ: $1",
        "badtitle": "ⴳⴰⵔ ⴰⵣⵡⵍ",
        "pt-login-button": "ⴽⵛⵎ",
        "pt-createaccount": "ⵙⵏⴼⵍⵓⵍ ⴰⵎⵉⴹⴰⵏ",
        "pt-userlogout": "ⴼⴼⵖ",
+       "newpassword": "ⵜⴰⴳⵓⵔⵉ ⵏ ⵓⵣⵔⴰⵢ ⵜⴰⵎⴰⵢⵏⵓⵜ",
        "botpasswords-label-create": "ⵙⵏⵓⵍⴼⵓ",
        "botpasswords-label-delete": "ⴽⴽⵙ",
        "passwordreset": "ⵔⴰⵔ ⴷ ⵜⴰⴳⵓⵔⵉ ⵏ ⵓⵣⵔⴰⵢ",
+       "changeemail-newemail": "ⵉⵎⴰⵢⵍ ⴰⵎⴰⵢⵏⵓ:",
+       "changeemail-none": "(ⵓⵍⴰ ⵢⴰⵏ)",
        "bold_sample": "ⴰⴹⵔⵉⵙ ⴰⵣⵓⵔⴰⵔ",
        "bold_tip": "ⴰⴹⵔⵉⵙ ⴰⵣⵓⵔⴰⵔ",
        "italic_sample": "ⴰⴹⵔⵉⵙ ⵓⵣⵍⵉⴳ",
        "lineno": "ⵉⵣⵔⵉⵔⵉ $1:",
        "compareselectedversions": "ⵙⵎⵣⴰⵣⴰⵍ ⵉⵣⵣⵔⴰⵢⵏ ⵉⵜⵜⵓⵙⵜⴰⵢⵏ",
        "editundo": "ⵙⵔ",
+       "diff-empty": "(ⵓⵔ ⵉⵍⵍⵉ ⵓⵎⵣⴰⵔⴰⵢ)",
        "searchresults": "ⵜⵉⵢⴰⴼⵓⵜⵉⵏ ⵏ ⵓⵔⵣⵣⵓ",
        "searchresults-title": "ⵜⵉⵢⴰⴼⵓⵜⵉⵏ ⵏ ⵓⵔⵣⵣⵓ ⵖⴼ \"$1\"",
        "prevn": "{{PLURAL:$1|$1}} ⵉⵎⵣⵡⵓⵔⴰ",
        "searchprofile-advanced-tooltip": "ⵔⵣⵓ ⴳ ⵜⵉⵔⵉⵡⵉⵏ ⵏ ⵉⵙⵎⴰⵡⵏ ⵉⵜⵡⴰⵏⵉⵎⴰⵏ",
        "search-result-size": "$1 ({{PLURAL:$2|1 ⵜⴳⵓⵔⵉ|$2 ⵜⴳⵓⵔⵉⵡⵉⵏ}})",
        "search-redirect": "(ⵓⵖⵓⵍ ⵙⴳ $1)",
+       "search-section": "(ⴰⵙⴱⴹⵓ $1)",
        "search-suggest": "ⵉⵙ ⵜⵅⵙⴷ ⴰⴷ ⵜⵉⵏⵉⴷ: $1",
        "search-interwiki-more": "(ⵓⴳⴳⴰⵔ)",
        "searchall": "ⴰⴽⴽ",
        "newuserlogpage": "ⴰⵔⵔⴰ ⵏ ⵉⵙⵏⴼⴰⵍⵏ ⵏ ⵉⵎⵉⴹⴰⵏⴻⵏ ⵏ ⵉⵙⵙⵎⵔⴰⵙⵏ",
        "action-edit": "ⵙⵏⴼⵍ ⵜⴰⵙⵏⴰ ⴰ",
        "action-createpage": "ⵙⵏⵓⵍⴼⵓ ⵜⴰⵙⵏⴰ ⴰ",
+       "action-createaccount": "ⵙⴽⵔ ⴰⵎⵉⴹⴰⵏ ⴰⴷ ⵏ ⵓⵏⵙⵙⵎⵔⵙ",
        "enhancedrc-history": "ⴰⵎⵣⵔⴰⵢ",
        "recentchanges": "ⵉⵙⵏⴼⵍⵏ ⵉⵎⴳⴳⵓⵔⴰ",
        "recentchanges-legend": "ⵜⵉⴷⵖⵔⵉⵏ ⵏ ⵉⵙⵏⴼⵍⵏ ⵉⵎⴳⴳⵓⵔⴰ",
+       "recentchanges-summary": "ⴹⴼⵔ ⵉⵙⵏⵉⴼⵉⵍⵏ ⵉⵎⴳⴳⵓⵔⴰ ⴰⴽⴽ ⵖⴼ ⵓⵡⵉⴽⵉ ⴷⴳ ⵜⴰⵙⵏⴰ ⴰⴷ.",
        "recentchanges-label-newpage": "ⵉⵙⵏⴼⵍⵓⵍ ⵓⵙⵏⴼⵍ ⴰ ⵢⴰⵜ ⵜⴰⵙⵏⴰ ⵜⴰⵎⴰⵢⵏⵓⵜ",
        "recentchanges-label-minor": "ⵡⴰ ⴷ ⴰⵙⵏⴼⵍ ⵓⵎⵥⵉⵢ",
        "recentchanges-label-bot": "ⴰⵙⵏⴼⵍ ⴰⴷ ⵉⵜⵡⴰⵙⴽⴰⵔ ⵙ ⵓⴱⵓⵜ",
        "allpages": "ⵎⴰⵕⵕⴰ ⵜⴰⵙⵏⵉⵡⵉⵏ",
        "allarticles": "ⵜⴰⵙⵏⵉⵡⵉⵏ ⴰⴽⴽ",
        "allpagessubmit": "ⴷⴷⵓ",
+       "allpages-hide-redirects": "ⵙⵙⵏⵜⵍ ⵉⵙⵡⴰⵍⴰⵜⵏ",
        "categories": "ⵉⵙⵎⵉⵍⵏ",
        "sp-deletedcontributions-contribs": "ⵜⵓⵎⵓⵜⵉⵏ",
        "listgrouprights-members": "ⵜⴰⵍⴳⴰⵎⵜ ⵏ ⵉⴳⵎⴰⵎⵏ",
+       "usermessage-editor": "ⵓⴷⵓⵙ ⵏ ⵓⵎⵢⴰⵣⴰⵏ",
        "watchlist": "ⵜⴰⵍⴳⴰⵎⵜ ⵏ ⵓⴹⴼⴼⵓⵔ",
        "mywatchlist": "ⵜⴰⵍⴳⴰⵎⵜ ⵏ ⵓⴹⴼⴼⵓⵔ",
        "watchlistfor2": "ⵉ $1 $2",
        "watch": "ⵥⵕ",
        "wlshowlast": "ⵙⴽⵏ $1 ⵜⴰⵙⵔⴰⴳⵉⵏ $2 ⵓⵙⵙⴰⵏ ⵉⵎⴳⴳⵓⵔⴰ",
        "watchlist-options": "ⵜⵉⴷⵖⵔⵉⵏ ⵏ ⵜⵍⴳⴰⵎⵜ ⵏ ⵓⴹⴼⴼⵓⵔ",
+       "enotif_reset": "ⴷⵔⵣ ⵜⴰⵙⵏⵉⵡⵉⵏ ⴰⴽⴽ ⵏⵏⴰ ⵜⵔⵣⴼⴷ",
        "deletepage": "ⴽⴽⵙ ⵜⴰⵙⵏⴰ",
        "delete-confirm": "ⴽⴽⵙ \"$1\"",
        "delete-legend": "ⴽⴽⵙ",
        "mycontris": "ⵜⵓⵎⵓⵜⵉⵏ",
        "anoncontribs": "ⵜⵓⵎⵓⵜⵉⵏ",
        "contribsub2": "ⵉ {{GENDER:$3|$1}} ($2)",
+       "uctop": "(ⴰⵎⵉⵔⴰⵏ)",
        "month": "ⵙⴳ ⵡⴰⵢⵢⵓⵔ (and earlier):",
        "year": "ⵙⴳ ⵓⵙⴳⴳⵯⴰⵙ (and earlier):",
        "sp-contributions-newbies": "ⵙⴽⵏ ⵜⵓⵎⵓⵜⵉⵏ ⵏ ⵉⵎⵉⴹⴰⵏ ⵉⵎⴰⵢⵏⵓⵜⵏ ⴽⴰⵏ",
        "sp-contributions-uploads": "ⵉⵙⴽⵜⴰⵔⵏ",
        "sp-contributions-talk": "ⵎⵙⴰⵡⴰⵍ",
        "sp-contributions-search": "ⵔⵣⵓ ⵖⴼ ⵜⵓⵎⵓⵜⵉⵏ",
+       "sp-contributions-username": "ⵜⴰⵏⵙⴰ ⵏ IP ⵏⵖ ⵉⵙⵎ ⵓⵎⵔⵉⵙ:",
+       "sp-contributions-toponly": "ⵙⴽⵏ ⵖⴰⵙ ⵉⵙⵏⵉⴼⵉⵍⵏ ⵉⴳⴰⵏ ⵉⵣⵣⵔⴰⵢⵏ ⵉⵎⴳⴳⵓⵔⴰ",
        "sp-contributions-newonly": "ⵙⴽⵏ ⵖⴰⵙ ⵉⵙⵏⵍⵏ ⵏⵏⴰ ⵉⴳⴰⵏ ⵉⵙⵏⵓⵍⴼⵓⵜⵏ ⵏ ⵜⴰⵙⵏⴰ",
        "sp-contributions-submit": "ⵔⵣⵓ",
        "whatlinkshere": "ⵎⴰ ⴰⵢⴷ ⵉⵇⵇⵏⵏ ⵙ ⴷⴰ",
        "whatlinkshere-page": "ⵜⴰⵙⵏⴰ:",
        "linkshere": "ⵜⴰⵙⵏⵉⵡⵉⵏ ⴰⴷ ⵣⴷⵉⵏ ⵖⵔ <strong>[[:$1]]</strong>:",
        "nolinkshere": "ⵓⵔ ⵍⵍⵉⵏ ⵜⴰⵙⵏⵉⵡⵉⵏ ⵉⵣⴷⵉⵏ ⵖⵔ <strong>[[:$1]]</strong>",
+       "isredirect": "ⵙⵡⴰⵍⴰ ⵜⴰⵙⵏⴰ",
+       "istemplate": "ⴰⵙⵙⵓⵎⵢ",
        "isimage": "ⴰⵙⵖⵓⵏ ⵏ ⵓⴼⴰⵢⵍⵓ",
        "whatlinkshere-links": "← ⵉⵙⵖⵓⵏⴻⵏ",
+       "whatlinkshere-hidetrans": "$1 ⵉⵙⵙⵓⵎⵢⵏ",
        "whatlinkshere-hidelinks": "$1 ⵉⵙⵖⵓⵏⴻⵏ",
        "whatlinkshere-hideimages": "$1 ⵉⵣⴷⴰⵢⵏ ⵖⵔ ⵓⴼⵉⵍⵢⵓ",
        "whatlinkshere-filters": "ⵜⵉⵙⵜⵜⴰⵢⵉⵏ",
        "ipbreason": "ⵜⴰⵎⵏⵜⵉⵍⵜ:",
-       "ipboptions": "2 âµ\8f âµ\9câµ\99âµ\94â´°â´³âµ\89âµ\8f:2 âµ\8f âµ\9câµ\99âµ\94â´°â´³âµ\89âµ\8f,1 âµ\8f âµ¡â´°âµ\99âµ\99:1 âµ\8f âµ¡â´°âµ\99âµ\99,3 âµ\8f âµ¡âµ\93âµ\99âµ\99â´°âµ\8f:3 âµ\8f âµ¡âµ\93âµ\99âµ\99â´°âµ\8f,1 âµ\8f âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99:1 âµ\8f âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99k,2 âµ\8f âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99âµ\8f:2 âµ\8f âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99âµ\8f,1 âµ\8f âµ¡â´°âµ¢âµ¢âµ\93âµ\94:1 âµ\8f âµ¡â´°âµ¢âµ¢âµ\93âµ\94,3 âµ\8f âµ¡â´°âµ¢âµ¢âµ\93âµ\94âµ\8f:3 âµ\8f âµ¡â´°âµ¢âµ¢âµ\93âµ\94âµ\8f,6 âµ\8f âµ¡â´°âµ¢âµ¢âµ\93âµ\94âµ\8f:6 âµ\8f âµ¡â´°âµ¢âµ¢âµ\93âµ\94âµ\8f,1 âµ\8f âµ\93âµ\99ⴳⴳⴰâµ\99:1 âµ\8f âµ\93âµ\99ⴳⴳⴰâµ\99,indefinite:infinite",
+       "ipboptions": "2 âµ\9câµ\99âµ\94â´°â´³âµ\89âµ\8f:2 âµ\9câµ\99âµ\94â´°â´³âµ\89âµ\8f,1 âµ¡â´°âµ\99âµ\99:1 âµ¡â´°âµ\99âµ\99,3 âµ¡âµ\93âµ\99âµ\99â´°âµ\8f:3 âµ¡âµ\93âµ\99âµ\99â´°âµ\8f,1 âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99:1 âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99,2 âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99âµ\8f:2 âµ\89âµ\8eâ´°âµ\8dâ´°âµ\99âµ\99âµ\8f,1 âµ¡â´°âµ¢âµ¢âµ\93âµ\94:1 âµ¡â´°âµ¢âµ¢âµ\93âµ\94,3 âµ\89ⵢⵢâµ\89âµ\94âµ\8f:3 âµ\89ⵢⵢâµ\89âµ\94âµ\8f,6 âµ\89ⵢⵢâµ\89âµ\94âµ\8f:6 âµ\89ⵢⵢâµ\89âµ\94âµ\8f,1 âµ\93âµ\99ⴳⴳⴰâµ\99:1 âµ\93âµ\99ⴳⴳⴰâµ\99,â´°âµ\94âµ\93âµ\99âµ\8eâµ\89âµ\8d:â´°âµ\94âµ\93âµ\99âµ\8eâµ\89âµ\8d",
        "blocklist-reason": "ⵜⴰⵎⵏⵜⵉⵍⵜ",
        "blocklink": "ⴳⴷⵍ",
        "contribslink": "ⵜⵓⵎⵓⵜⵉⵏ",
        "movereason": "ⵜⴰⵎⵏⵜⵉⵍⵜ:",
        "delete_and_move_confirm": "ⵢⴰⵀ, ⴽⴽⵙ ⵜⴰⵙⵏⴰ",
+       "export": "ⵙⵙⵓⴼⵖ ⵜⴰⵙⵏⵉⵡⵉⵏ",
        "allmessagesname": "ⵉⵙⵎ",
        "allmessages-language": "ⵜⵓⵜⵍⴰⵢⵜ:",
        "allmessages-filter-translate": "ⵙⵙⵓⵖⵍ",
        "tooltip-pt-userpage": "ⵜⴰⵙⵏⴰ ⵏ ⵓⵙⵎⵔⴰⵙ {{GENDER:|ⵏⵏⴽ|ⵏⵏⵎ}}",
        "tooltip-pt-mytalk": "ⵜⴰⵙⵏⴰ {{GENDER:|ⵏⵏⴽ|ⵏⵏⵎ}} ⵏ ⵓⵎⵙⴰⵡⴰⵍ",
        "tooltip-pt-preferences": "ⵉⵙⵎⵏⵢⵉⴼⵏ {{GENDER:|ⵏⵏⴽ|ⵏⵏⵎ}}",
+       "tooltip-pt-watchlist": "ⵢⴰⵜ ⵜⵍⴳⴰⵎⵜ ⵏ ⵜⴰⵙⵏⵉⵡⵉⵏ ⵏⵏⴰ ⵜⵎⵎⵓⵜⵔⴷ ⵉ ⵉⵙⵏⵉⴼⵉⵍⵏ",
        "tooltip-pt-mycontris": "ⵢⴰⵜ ⵜⵍⴳⴰⵎⵜ ⵏ ⵜⵓⵎⵓⵜⵉⵏ {{GENDER:|ⵏⵏⴽ|ⵏⵏⵎ}}",
        "tooltip-pt-login": "ⴰⵔⴽ ⵏⵙⵙⵔⵇⴰⴱ ⴰⵜⴽⵛⵎⵜ; ⵎⴰⵛ ⵓⵔ ⵉⴳⵉ ⵓⵣⵓⵛⵍⵍ",
        "tooltip-pt-logout": "ⴼⴼⵖ",
        "tooltip-save": "ⵃⴹⵓ ⵉⵙⵏⴼⴰⵍ ⵏⵏⴽ",
        "tooltip-preview": "ⵣⵔ ⵣⵡⴰⵔ ⵉⵙⵏⴼⵍⵏ ⵏⴽ. ⴼⴰⴷ ⴰⴷ ⵜⵏ ⵜⵙⵓⵙⵔⴷ.",
        "tooltip-diff": "ⵙⴽⵏ ⵎⴰⵏ ⵉⵙⵏⴼⴰⵍ ⵜⴳⴳⵉⴷ ⵉ ⵓⴹⵔⵉⵙ",
+       "tooltip-compareselectedversions": "ⵥⵔ ⴰⵎⵣⴰⵔⴰⵢ ⴳⵔ ⵙⵉⵏ ⵉⵣⵣⵔⴰⵢⵏ ⵉⵜⵜⵓⵙⵜⴰⵢⵏ ⵏ ⵜⴰⵙⵏⴰ ⴰⴷ",
        "tooltip-watch": "ⵔⵏⵓ ⵜⴰⵙⵏⴰ ⴰ ⵉ ⵜⵍⴳⴰⵎⵜ ⵏ ⵓⴹⴼⴼⵓⵔ {{GENDER:|ⵏⵏⴽ|ⵏⵏⵎ}}",
        "tooltip-rollback": "\"ⵔⴰⵔ\" ⵙⵙⵔ ⴰⵙⵏⴼⵍ ⵏⵖ ⵉⵙⵏⴼⴰⵍⵏ ⵏ ⵓⵎⴰⴷⵔⴰⵡ ⴰⵎⴳⴳⴰⵔⵓ ⴳ ⵜⴰⵙⵏⴰ ⴷ ⵙ ⵢⴰⵏ ⵓⴽⵍⵉⴽ",
        "tooltip-summary": "ⴰⵔⴰ ⴽⵔⴰ ⵏ ⵓⵙⴳⵣⵍ ⵎⵥⵥⵉⵢⵏ",
        "simpleantispam-label": "ⵜⵉⵎⵏⵥⵉⵜ ⵎⴳⵍ-ⴳⴰⵔⴰⵙⵎⵔⴰⵔⴰ.\nⴰⴷ <strong>ⵓⵔ</strong> ⵜⵣⵎⵎⴻⵎⴷ ⴰⵎⵢⴰ ⴳ ⵖⵉ!",
        "pageinfo-title": "ⵉⵏⵖⵎⵉⵙⵏ ⵖⴼ $1",
+       "pageinfo-header-basic": "ⵉⵏⵖⵎⵉⵙⵏ ⵏ ⵜⵙⵉⵍⴰ",
        "pageinfo-header-edits": "ⵙⵏⴼⵍ ⴰⵎⵣⵔⵓⵢ",
        "pageinfo-header-restrictions": "ⴰⴼⵔⴰⴳ ⵏ ⵜⴰⵙⵏⴰ",
        "pageinfo-display-title": "ⵙⴽⵏ ⴰⵣⵡⵍ",
        "pageinfo-article-id": "ID ⵏ ⵜⴰⵙⵏⴰ",
        "pageinfo-language": "ⵜⵓⵜⵍⴰⵢⵜ ⵏ ⵜⵙⵏⴰ",
+       "pageinfo-robot-index": "ⵉⵜⵜⵓⴼⵔⴰⴳ",
+       "pageinfo-robot-noindex": "ⵓⵔ ⵉⵜⵜⵓⴼⵔⴰⴳ",
        "pageinfo-watchers": "ⵓⵟⵟⵓⵏ ⵏ ⵉⵎⵥⵕⴰⵢⵏ ⵏ ⵜⴰⵙⵏⴰ",
+       "pageinfo-few-watchers": "ⴷⵔⵓⵙ ⵅⴼ $1 {{PLURAL:$1|ⴰⵎⴰⵏⵏⴰⵢ|ⵉⵎⴰⵏⵏⴰⵢⵏ}}",
+       "pageinfo-redirects-name": "ⵓⵟⵟⵓⵏ ⵏ ⵉⵙⵡⴰⵍⴰⵜⵏ ⵖⵔ ⵜⴰⵙⵏⴰ ⴰⴷ",
+       "pageinfo-subpages-name": "ⵓⵟⵟⵓⵏ ⵏ ⵜⴷⵓⵙⵏⵉⵡⵉⵏ ⵏ ⵜⴰⵙⵏⴰ ⴰⴷ",
        "pageinfo-firstuser": "ⴰⵎⵙⵏⵓⵍⴼⵓ ⵏ ⵜⴰⵙⵏⴰ",
        "pageinfo-firsttime": "ⴰⵙⴰⴽⵓⴷ ⵏ ⵓⵙⵏⴼⵍⵓⵍ ⵏ ⵜⴰⵙⵏⴰ",
        "pageinfo-lastuser": "ⴰⵎⵙⵏⴼⵍ ⴰⵎⴳⴳⴰⵔⵓ",
        "pageinfo-lasttime": "ⴰⵙⴰⴽⵓⴷ ⵏ ⵓⵙⵏⴼⵍ ⴰⵎⴳⴳⴰⵔⵓ",
        "pageinfo-edits": "ⵎⴰⵕⵕⴰ ⵓⵟⵟⵓⵏ ⵏ ⵉⵙⵏⴼⴰⵍⵏ",
+       "pageinfo-authors": "ⵓⵟⵟⵓⵏ ⴰⵎⵖⵔⵓⴷ ⵏ ⵉⵎⴰⵔⴰⵜⵏ ⵉⵎⵢⴰⵍⵍⴰⵏ",
+       "pageinfo-magic-words": "ⵉⵎⴽⵓⵔⴰⵔⵏ {{PLURAL:$1|ⵜⴰⴳⵓⵔⵉ|ⵜⵉⴳⵓⵔⵉⵡⵉⵏ}} ($1)",
        "pageinfo-hidden-categories": "ⵏⵜⵍ {{PLURAL:$1|ⴰⵙⵎⵉⵍ|ⵉⵙⵎⵉⵍⵏ}}($1)",
        "pageinfo-toolboxlink": "ⴰⵏⵖⵎⵉⵙ ⵖⴼ ⵜⴰⵙⵏⴰ",
        "pageinfo-contentpage": "ⵉⵜⵜⵓⵙⵉⴹⵏ ⴰⵎ ⵜⴰⵙⵏⴰ ⵏ ⵜⵓⵎⴰⵢⵜ",
        "exif-yresolution": "ⵜⵉⵙⴷⴷⵉ ⵜⴰⴱⴷⴷⴰⵢⵜ",
        "exif-datetime": "ⴰⵙⴰⴽⵓⴷ ⴷ ⵡⴰⴽⵓⴷ ⵏ ⵓⵙⵏⴼⵍ ⵏ ⵓⴼⴰⵢⵍⵓ",
        "exif-model": "ⴰⵏⴰⵡ ⵏ ⵜⵙⵡⵍⴰⴼⵜ",
+       "exif-software": "ⴰⵙⵖⵥⴰⵏ ⵉⵜⵜⵓⵙⵎⵔⵙⵏ",
        "exif-colorspace": "ⵜⵉⵔⵉⵡⵜ ⵏ ⵓⴽⵍⵓ",
+       "exif-datetimeoriginal": "ⴰⵙⴰⴽⵓⴷ ⴷ ⵜⵉⵣⵉ ⵏ ⵓⵙⴽⴽⵉⵔ ⵏ ⵉⵙⴼⴽⴰ",
+       "exif-datetimedigitized": "ⴰⵙⴰⴽⵓⴷ ⴷ ⵜⵉⵣⵉ ⵏ ⵓⵙⵓⵟⵟⵏ",
        "exif-languagecode": "ⵜⵓⵜⵍⴰⵢⵜ",
        "exif-orientation-1": "ⴰⵎⴳⵏⵓ",
        "exif-dc-contributor": "ⵉⵏⴰⵎⵓⵜⵏ",
        "confirm-watch-button": "ⵡⴰⵅⵅⴰ",
        "confirm-unwatch-button": "ⵡⴰⵅⵅⴰ",
        "confirm-rollback-button": "ⵡⴰⵅⵅⴰ",
+       "imgmultipagenext": "ⵜⴰⵙⵏⴰ ⵜⵉⵏⴹⴼⵔⵜ →",
        "imgmultigo": "ⴷⴷⵓ!",
        "imgmultigoto": "ⴷⴷⵓ ⵖⵔ ⵜⴰⵙⵏⴰ ⴰⴷ $1",
        "img-lang-default": "(ⵜⵓⵜⵍⴰⵢⵜ ⵙ ⵓⵡⵏⵓⵍ)",
        "watchlisttools-clear": "ⵙⴼⴹ ⵜⴰⵍⴳⴰⵎⵜ ⵏ ⵓⴹⴼⴼⵓⵔ",
+       "watchlisttools-view": "ⵙⴽⵏ ⵉⵙⵏⵉⴼⵉⵍⵏ ⴷ ⵢⵓⵙⴰⵏ",
        "watchlisttools-edit": "ⵥⵕ ⴷ ⵜⵙⵏⴼⵍⴷ ⵜⴰⵍⴳⴰⵎⵜ ⵏ ⵓⴹⴼⴼⵓⵔ",
        "redirect-submit": "ⴷⴷⵓ",
+       "redirect-value": "ⴰⵣⴰⵍ",
        "redirect-user": "ID ⵏ ⵓⵎⵙⵙⵎⵔⵙ",
        "redirect-page": "ID ⵏ ⵜⴰⵙⵏⴰ",
        "redirect-revision": "ⴰⵣⵣⵔⴰⵢ ⵏ ⵜⴰⵙⵏⴰ",
        "htmlform-no": "ⵓⵀⵓ",
        "htmlform-yes": "ⵢⴰⵀ",
        "logentry-delete-delete": "$1 {{GENDER:$2|ⵉⴽⴽⵙ|ⵜⴽⴽⵙ}} ⵜⴰⵙⵏⴰ $3",
+       "revdelete-content-hid": "ⵜⴻⵜⵜⵓⵏⵜⴰⵍ ⵜⵓⵎⴰⵢⵜ",
        "logentry-move-move": "$1 {{GENDER:$2|ⵉⵙⵎⵓⵜⵜⵉ|ⵜⵙⵎⵓⵜⵜⵉ}} ⵜⴰⵙⵏⴰ ⵙⴳ $3 ⵖⵔ $4",
        "logentry-move-move-noredirect": "{{GENDER:$2|ⵉⵙⵎⵓⵜⵜⵉ}} $1 ⵜⴰⵙⵏⴰ $3 ⵖⵔ $4 ⵎⵉⵏ ⴰⴷ ⵉⴼⵍ redirect",
        "logentry-newusers-create": "{{GENDER:$2|ⵉⵙⵏⴼⵍ ⵓⵏⵙⵙⵎⵔⵙ|ⵜⵙⵏⴼⵍ ⵜⵏⵙⵙⵎⵔⵙⵜ}} $1 ⴰⵎⵉⴹⴰⵏ ⵏⵙ",
        "logentry-upload-upload": "{{GENDER:$2|ⵉⵙⴽⵜⵔ|ⵜⵙⴽⵜⵔ}} $1 $3",
+       "rightsnone": "(ⵓⵍⴰ ⵢⴰⵏ)",
        "feedback-thanks-title": "ⵜⴰⵏⵎⵎⵉⵔⵜ!",
        "searchsuggest-search": "ⵔⵣⵓ ⴳ {{SITENAME}}",
        "duration-days": "$1 ⵏ {{PLURAL:$1|ⵡⴰⵙⵙ|ⵡⵓⵙⵙⴰⵏ}}",
index cea4151..f7ad13c 100644 (file)
        "recentchangeslinked-feed": "相关更改",
        "recentchangeslinked-toolbox": "相关更改",
        "recentchangeslinked-title": "与“$1”有关的更改",
-       "recentchangeslinked-summary": "输入页面名称以查看链入(或链自)相关页面的更改。(要查看分类的成员,请输入Category:分类名称)。对[[Special:Watchlist|您的监视列表]]上页面的更改以<strong>粗体</strong>显示。",
+       "recentchangeslinked-summary": "输入页面名称以查看链入(或链自)相关页面的更改。(要查看分类的成员,请输入{{ns:category}}:分类名称)。对[[Special:Watchlist|您的监视列表]]上页面的更改以<strong>粗体</strong>显示。",
        "recentchangeslinked-page": "页面名称:",
        "recentchangeslinked-to": "显示链到所给出的页面",
        "recentchanges-page-added-to-category": "[[:$1]]已添加至分类",
        "exif-copyrighted-false": "版权状态未设定",
        "exif-photometricinterpretation-0": "黑白(白为0)",
        "exif-photometricinterpretation-1": "黑白(黑为0)",
+       "exif-photometricinterpretation-4": "透明遮罩",
+       "exif-photometricinterpretation-5": "分隔(可能是CMYK)",
+       "exif-photometricinterpretation-32803": "色彩滤镜矩阵",
        "exif-unknowndate": "未知日期",
        "exif-orientation-1": "标准",
        "exif-orientation-2": "水平翻转",
        "watchlisttools-view": "查看相关更改",
        "watchlisttools-edit": "查看并编辑监视列表",
        "watchlisttools-raw": "编辑原始监视列表",
+       "hijri-calendar-m1": "穆哈兰姆月",
+       "hijri-calendar-m2": "色法尔月",
+       "hijri-calendar-m3": "赖比尔·敖外鲁月",
+       "hijri-calendar-m4": "赖比尔·阿色尼月",
+       "hijri-calendar-m5": "主马达·敖外鲁月",
+       "hijri-calendar-m6": "主马达·阿色尼月",
+       "hijri-calendar-m7": "赖哲卜月",
+       "hijri-calendar-m8": "舍尔邦月",
+       "hijri-calendar-m9": "赖买丹月",
+       "hijri-calendar-m10": "闪瓦鲁月",
+       "hijri-calendar-m11": "都尔喀尔德月",
+       "hijri-calendar-m12": "都尔黑哲月",
+       "hebrew-calendar-m1": "提斯利月",
+       "hebrew-calendar-m2": "玛西班月",
+       "hebrew-calendar-m3": "基斯流月",
+       "hebrew-calendar-m4": "提别月",
+       "hebrew-calendar-m5": "细罢特月",
+       "hebrew-calendar-m6": "亚达月",
+       "hebrew-calendar-m6a": "第一亚达月",
+       "hebrew-calendar-m6b": "第二亚达月",
+       "hebrew-calendar-m7": "尼散月",
+       "hebrew-calendar-m8": "以珥月",
+       "hebrew-calendar-m9": "西弯月",
+       "hebrew-calendar-m10": "搭模斯月",
+       "hebrew-calendar-m11": "埃波月",
+       "hebrew-calendar-m12": "以禄月",
        "signature": "[[{{ns:user}}:$1|$2]]([[{{ns:user_talk}}:$1|讨论]])",
        "timezone-local": "本地",
        "duplicate-defaultsort": "<strong>警告:</strong>默认排序关键词“$2”覆盖了之前的默认排序关键词“$1”。",
index 5d8e44c..3a14a15 100644 (file)
@@ -96,7 +96,8 @@
                        "Laundry Machine",
                        "和平至上",
                        "Sanmosa",
-                       "Dongzn"
+                       "Dongzn",
+                       "Shangkuanlc"
                ]
        },
        "tog-underline": "底線標示連結:",
index b0389b9..f16aab4 100644 (file)
 {
-    "@metadata": {
-        "authors": [
-            "Horacewai2",
-            "Kayau",
-            "Mark85296341",
-            "PhiLiP",
-            "Shizhao",
-            "Waihorace",
-            "Wong128hk",
-            "Yukiseaside",
-            "Yuyu"
-        ]
-    },
-    "tog-watchlisthidebots": "監視列表中隱藏機械人的編輯",
-    "january": "一月",
-    "february": "二月",
-    "march": "三月",
-    "april": "四月",
-    "may_long": "五月",
-    "june": "六月",
-    "july": "七月",
-    "august": "八月",
-    "september": "九月",
-    "october": "十月",
-    "november": "十一月",
-    "december": "十二月",
-    "mytalk": "我的討論頁",
-    "tagline": "從 {{SITENAME}}",
-    "search": "搜尋",
-    "printableversion": "可打印版",
-    "permalink": "永久連接",
-    "print": "打印",
-    "specialpage": "特殊頁面",
-    "jumpto": "跳到:",
-    "jumptosearch": "搜尋",
-    "aboutpage": "Project:關於我們",
-    "privacy": "私隱政策",
-    "privacypage": "Project:私隱政策",
-    "red-link-title": "$1 (頁面不存在)",
-    "nstab-special": "特殊頁面",
-    "nav-login-createaccount": "登入/創造帳戶",
-    "userlogin": "登入/創造帳戶",
-    "editing": "正在編輯 $1",
-    "rev-deleted-comment": "(註釋已除)",
-    "rev-deleted-event": "(日誌已除)",
-    "revdelete-suppress-text": "壓制'''只'''應用於以下的情況:\n* 不合適的個人資料\n*: ''地址、電話號碼、身份證號碼等。''",
-    "editundo": "撤銷",
-    "search-mwsuggest-disabled": "沒有意見",
-    "prefs-help-gender": "可選:用於軟件中的性別指定。此項資料將會被公開。",
-    "group-bot": "機械人",
-    "group-bot-member": "機械人",
-    "grouppage-bot": "{{ns:project}}:機械人",
-    "recentchanges-label-bot": "這次編輯是由機械人進行",
-    "rcshowhidebots": "$1機械人的編輯",
-    "activeusers-hidebots": "隱藏機械人",
-    "contribslink": "貢獻",
-    "tooltip-search": "搜尋 {{SITENAME}}",
-    "tooltip-search-go": "若是真有其頁,則進入相同名字的頁面",
-    "tooltip-search-fulltext": "在此頁面內搜尋此文字",
-    "tooltip-n-mainpage": "回到首頁",
-    "tooltip-n-mainpage-description": "回到首頁",
-    "tooltip-n-randompage": "跳到一個隨機抽取的頁面",
-    "tooltip-t-print": "這個頁面的可打印版本",
-    "showhidebots": "($1機械人)",
-    "specialpages": "特殊頁面"
+       "@metadata": {
+               "authors": [
+                       "Horacewai2",
+                       "Kayau",
+                       "Mark85296341",
+                       "PhiLiP",
+                       "Shizhao",
+                       "Waihorace",
+                       "Wong128hk",
+                       "Yukiseaside",
+                       "Yuyu",
+                       "Hello903hello",
+                       "Justincheng12345",
+                       "LNDDYL",
+                       "Liuxinyu970226",
+                       "Quest for Truth"
+               ]
+       },
+       "tog-watchlisthidebots": "隱藏監視清單中機械人的編輯",
+       "january": "一月",
+       "february": "二月",
+       "march": "三月",
+       "april": "四月",
+       "may_long": "五月",
+       "june": "六月",
+       "july": "七月",
+       "august": "八月",
+       "september": "九月",
+       "october": "十月",
+       "november": "十一月",
+       "december": "十二月",
+       "mytalk": "我的討論頁",
+       "tagline": "從 {{SITENAME}}",
+       "search": "搜尋",
+       "printableversion": "可打印版",
+       "permalink": "靜態連結",
+       "print": "打印",
+       "specialpage": "特殊頁面",
+       "jumpto": "跳到:",
+       "jumptosearch": "搜尋",
+       "aboutpage": "Project:關於我們",
+       "privacy": "私隱政策",
+       "privacypage": "Project:私隱政策",
+       "red-link-title": "$1 (頁面不存在)",
+       "nstab-user": "用戶頁面",
+       "nstab-special": "特殊頁面",
+       "nstab-template": "模板",
+       "editinginterface": "<strong>警告:</strong>您正在編輯的頁面文字是用來作為軟件介面使用。更改此頁面將會影響其他用戶在此 Wiki 上看到的用戶介面。",
+       "yourname": "用戶名稱:",
+       "userlogin-yourname": "用戶名稱",
+       "nav-login-createaccount": "登入/創造帳戶",
+       "wrongpassword": "您輸入的密碼有錯誤,請再試一次。",
+       "botpasswords": "機械人密碼",
+       "botpasswords-summary": "<em>機械人密碼</em>可在不使用帳號的主要登入密碼情況下,允許透過 API 存取使用者帳號。使用機械人密碼登入的使用者權限或會有所限制。\n\n若你並非必須設定此密碼,你便不應使用此功能。任何人都不會索取你的機械人密碼。",
+       "botpasswords-disabled": "機械人密碼已停用。",
+       "botpasswords-no-central-id": "要使用機械人密碼,您必須登入帳號集中管理系統。",
+       "botpasswords-existing": "現有機械人密碼",
+       "botpasswords-createnew": "建立新機械人密碼",
+       "botpasswords-editexisting": "編輯現有機械人密碼",
+       "botpasswords-bad-appid": "機械人名稱「$1」無效。",
+       "botpasswords-insert-failed": "無法新增機械人名稱「$1」,是否早已加入?",
+       "botpasswords-update-failed": "無法刪除機械人名稱「$1」,是否早已刪除?",
+       "botpasswords-created-body": "{{GENDER:$2|使用者}}「$2」的機械人「$1」的機械人密碼已建立。",
+       "botpasswords-updated-body": "{{GENDER:$2|使用者}}「$2」的機械人「$1」的機械人密碼已更新。",
+       "botpasswords-deleted-body": "{{GENDER:$2|使用者}}「$2」的機械人「$1」的機械人密碼已刪除。",
+       "botpasswords-newpassword": "用來登入<strong>$1</strong>的新密碼為<strong>$2</strong>。<em>請記錄此密碼以供未來參考使用。</em><br>(舊式機械人的登入名稱需與最終使用者名稱相同,您亦可使用<strong>$3</strong>作為使用者名稱,<strong>$4</strong>作為密碼。)",
+       "botpasswords-restriction-failed": "機械人密碼限制已拒絕是次登入。",
+       "passwordreset-username": "用戶名稱:",
+       "editing": "正在編輯 $1",
+       "rev-deleted-comment": "(註釋已除)",
+       "rev-deleted-event": "(日誌已除)",
+       "revdelete-suppress-text": "壓制'''只'''應用於以下的情況:\n* 不合適的個人資料\n*: ''地址、電話號碼、身份證號碼等。''",
+       "editundo": "撤銷",
+       "prefs-user-pages": "用戶頁面",
+       "username": "{{GENDER:$1|用戶名稱}}:",
+       "prefs-help-gender": "可選:用於軟件中的性別指定。此項資料將會被公開。",
+       "group-user": "用戶",
+       "group-autoconfirmed": "自動確認用戶",
+       "group-bot": "機械人",
+       "group-user-member": "{{GENDER:$1|用戶}}",
+       "group-autoconfirmed-member": "{{GENDER:$1|自動確認用戶}}",
+       "group-bot-member": "機械人",
+       "grouppage-user": "{{ns:project}}:用戶",
+       "grouppage-bot": "{{ns:project}}:機械人",
+       "right-viewmyprivateinfo": "編輯自己的私隱數據(如:電子郵件位址及真實姓名)",
+       "right-editmyprivateinfo": "編輯自己的私隱數據(如:電子郵件位址及真實姓名)",
+       "action-block": "封鎖此用戶的編輯權限",
+       "recentchanges-label-bot": "此編輯由機械人執行",
+       "rcfilters-filter-bots-label": "機械人",
+       "rcfilters-filter-humans-label": "人類(非機械人)",
+       "rcshowhidebots": "$1機械人的編輯",
+       "rcshowhideliu": "$1 已註冊的用戶",
+       "rcshowhideanons": "$1 匿名用戶",
+       "upload": "上載檔案",
+       "listfiles_user": "用戶",
+       "filehist-user": "用戶",
+       "uncategorizedtemplates": "待分類模板",
+       "listusers": "用戶清單",
+       "newpages-username": "用戶名稱:",
+       "speciallogtitlelabel": "目標 (標題或用戶):",
+       "checkbox-select": "選擇: $1",
+       "emailusername": "用戶名稱:",
+       "wlshowhidebots": "機械人",
+       "blockip": "封鎖{{GENDER:$1|用戶}}",
+       "contribslink": "貢獻",
+       "blocklogtext": "這是用戶的封禁與解禁操作的日誌記錄。\n自動封禁的 IP 位址不予包含。\n請參考 [[Special:BlockList|封禁清單]] 以檢視目前的封禁。",
+       "tooltip-search": "搜尋 {{SITENAME}}",
+       "tooltip-search-go": "若是真有其頁,則進入相同名字的頁面",
+       "tooltip-search-fulltext": "在此頁面內搜尋此文字",
+       "tooltip-n-mainpage": "回到首頁",
+       "tooltip-n-mainpage-description": "回到首頁",
+       "tooltip-n-randompage": "跳到一個隨機抽取的頁面",
+       "tooltip-t-contributions": "此用戶的貢獻清單",
+       "tooltip-t-upload": "上載檔案",
+       "tooltip-t-print": "這個頁面的可打印版本",
+       "tooltip-ca-nstab-user": "檢視用戶頁面",
+       "newimages-showbots": "顯示機械人上傳的檔案",
+       "exif-rowsperstrip": "每帶行數",
+       "redirect-user": "用戶 ID",
+       "specialpages": "特殊頁面",
+       "tags-source-manual": "由使用者與機械人手動套用",
+       "logentry-managetags-activate": "$1{{GENDER:$2|已啟用}}標籤「$4」供使用者與機械人使用"
 }
diff --git a/languages/messages/MessagesAbs.php b/languages/messages/MessagesAbs.php
new file mode 100644 (file)
index 0000000..8cbe79b
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+/** Ambonese Malay (Bahasa Ambon)
+ *
+ * To improve a translation please visit https://translatewiki.net
+ *
+ * @ingroup Language
+ * @file
+ *
+ */
+
+$fallback = 'id';
index 3b53fb4..0ce9aa3 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-/** Korean (한국어(조선))
+/** Korean (Democratic People's Republic of Korea) (조선말)
  *
  * To improve a translation please visit https://translatewiki.net
  *
index 43e876e..c0c6284 100644 (file)
@@ -23,6 +23,8 @@
 
 require_once __DIR__ . '/Maintenance.php';
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Maintenance script that reports the hostname of a replica DB server.
  *
@@ -43,7 +45,7 @@ class GetSlaveServer extends Maintenance {
                        $db = $this->getDB( DB_REPLICA, $this->getOption( 'group' ) );
                        $host = $db->getServer();
                } else {
-                       $lb = wfGetLB();
+                       $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
                        $i = $lb->getReaderIndex();
                        $host = $lb->getServerName( $i );
                }
index f7ef7a2..1b9dac0 100644 (file)
@@ -24,6 +24,8 @@
 
 require_once __DIR__ . '/Maintenance.php';
 
+use MediaWiki\MediaWikiServices;
+
 class InitEditCount extends Maintenance {
        public function __construct() {
                parent::__construct();
@@ -48,7 +50,8 @@ in the load balancer, usually indicating a replication environment.' );
                } elseif ( $this->hasOption( 'quick' ) ) {
                        $backgroundMode = false;
                } else {
-                       $backgroundMode = wfGetLB()->getServerCount() > 1;
+                       $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+                       $backgroundMode = $lb->getServerCount() > 1;
                }
 
                $actorQuery = ActorMigration::newMigration()->getJoin( 'rev_user' );
index 91e0bc1..4c02998 100644 (file)
@@ -35,8 +35,7 @@
        </script>
        <script>
                // Mock startup.js
-               var mwPerformance = { mark: function () {} },
-                       mwNow = Date.now;
+               var mwNow = Date.now;
 
                function startUp() {
                        mw.config = new mw.Map();
index f041e15..08df8dc 100644 (file)
@@ -23,6 +23,8 @@
 
 require_once __DIR__ . '/Maintenance.php';
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Maintenance script to show database lag.
  *
@@ -36,8 +38,8 @@ class DatabaseLag extends Maintenance {
        }
 
        public function execute() {
+               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
                if ( $this->hasOption( 'r' ) ) {
-                       $lb = wfGetLB();
                        echo 'time     ';
 
                        $serverCount = $lb->getServerCount();
@@ -58,7 +60,6 @@ class DatabaseLag extends Maintenance {
                                sleep( 5 );
                        }
                } else {
-                       $lb = wfGetLB();
                        $lags = $lb->getLagTimes();
                        foreach ( $lags as $i => $lag ) {
                                $name = $lb->getServerName( $i );
index 65ede90..0c78712 100644 (file)
@@ -1091,7 +1091,7 @@ CREATE TABLE /*_*/interwiki (
   -- The URL of the file api.php
   iw_api nvarchar(max) NOT NULL,
 
-  -- The name of the database (for a connection to be established with wfGetLB( 'wikiid' ))
+  -- The name of the database (for a connection to be established with LBFactory::getMainLB( 'wikiid' ))
   iw_wikiid nvarchar(64) NOT NULL,
 
   -- A boolean value indicating whether the wiki is in this project
index ffa4ff7..dcb89d1 100644 (file)
@@ -103,7 +103,7 @@ class PopulateRevisionLength extends LoggedUpdateMaintenance {
                                                "{$prefix}_len IS NULL",
                                                $dbr->makeList( [
                                                        "{$prefix}_len = 0",
-                                                       "{$prefix}_sha1 != \"phoiac9h4m842xq45sp7s6u21eteeq1\"", // sha1( "" )
+                                                       "{$prefix}_sha1 != " . $dbr->addQuotes( 'phoiac9h4m842xq45sp7s6u21eteeq1' ), // sha1( "" )
                                                ], IDatabase::LIST_AND )
                                        ], IDatabase::LIST_OR )
                                ],
index a2e8e41..bd0a6b8 100644 (file)
@@ -4,5 +4,5 @@
 ALTER TABLE archive
   DROP COLUMN ar_text,
   DROP COLUMN ar_flags,
-  ALTER COLUMN ar_text_id SET DEFAULT 0;
+  ALTER COLUMN ar_text_id SET DEFAULT 0,
   ALTER COLUMN ar_text_id SET NOT NULL;
index 5a537aa..91a5f3b 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup Maintenance ExternalStorage
  */
 
+use MediaWiki\MediaWikiServices;
+
 require_once __DIR__ . '/../Maintenance.php';
 
 /**
@@ -36,51 +38,25 @@ class DumpRev extends Maintenance {
        }
 
        public function execute() {
-               $dbr = $this->getDB( DB_REPLICA );
-               $row = $dbr->selectRow(
-                       [ 'text', 'revision' ],
-                       [ 'old_flags', 'old_text' ],
-                       [ 'old_id=rev_text_id', 'rev_id' => $this->getArg() ]
-               );
-               if ( !$row ) {
+               $id = (int)$this->getArg();
+
+               $lookup = MediaWikiServices::getInstance()->getRevisionLookup();
+               $rev = $lookup->getRevisionById( $id );
+               if ( !$rev ) {
                        $this->fatalError( "Row not found" );
                }
 
-               $flags = explode( ',', $row->old_flags );
-               $text = $row->old_text;
-               if ( in_array( 'external', $flags ) ) {
-                       $this->output( "External $text\n" );
-                       if ( preg_match( '!^DB://(\w+)/(\w+)/(\w+)$!', $text, $m ) ) {
-                               $es = ExternalStore::getStoreObject( 'DB' );
-                               $blob = $es->fetchBlob( $m[1], $m[2], $m[3] );
-                               if ( strtolower( get_class( $blob ) ) == 'concatenatedgziphistoryblob' ) {
-                                       $this->output( "Found external CGZ\n" );
-                                       $blob->uncompress();
-                                       $this->output( "Items: (" . implode( ', ', array_keys( $blob->mItems ) ) . ")\n" );
-                                       $text = $blob->getItem( $m[3] );
-                               } else {
-                                       $this->output( "CGZ expected at $text, got " . gettype( $blob ) . "\n" );
-                                       $text = $blob;
-                               }
-                       } else {
-                               $this->output( "External plain $text\n" );
-                               $text = ExternalStore::fetchFromURL( $text );
-                       }
-               }
-               if ( in_array( 'gzip', $flags ) ) {
-                       $text = gzinflate( $text );
-               }
-               if ( in_array( 'object', $flags ) ) {
-                       $obj = unserialize( $text );
-                       $text = $obj->getText();
+               $content = $rev->getContent( 'main' );
+               if ( !$content ) {
+                       $this->fatalError( "Text not found" );
                }
 
-               if ( is_object( $text ) ) {
-                       $this->error( "Unexpectedly got object of type: " . get_class( $text ) );
-               } else {
-                       $this->output( "Text length: " . strlen( $text ) . "\n" );
-                       $this->output( substr( $text, 0, 100 ) . "\n" );
-               }
+               $blobStore = MediaWikiServices::getInstance()->getBlobStore();
+               $slot = $rev->getSlot( 'main' );
+               $text = $blobStore->getBlob( $slot->getAddress() );
+
+               $this->output( "Text length: " . strlen( $text ) . "\n" );
+               $this->output( substr( $text, 0, 100 ) . "\n" );
        }
 }
 
index df3264a..8c9be05 100644 (file)
@@ -1540,7 +1540,7 @@ CREATE TABLE /*_*/interwiki (
   -- The URL of the file api.php
   iw_api blob NOT NULL,
 
-  -- The name of the database (for a connection to be established with wfGetLB( 'wikiid' ))
+  -- The name of the database (for a connection to be established with LBFactory::getMainLB( 'wikiid' ))
   iw_wikiid varchar(64) NOT NULL,
 
   -- A boolean value indicating whether the wiki is in this project
index 1c6f9b3..1491e62 100644 (file)
@@ -24,6 +24,9 @@
 
 require_once __DIR__ . '/Maintenance.php';
 
+use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\DBReplicationWaitError;
+
 /**
  * Maintenance script to update cached special pages.
  *
@@ -119,16 +122,22 @@ class UpdateSpecialPages extends Maintenance {
         * mysql connection to "go away"
         */
        private function reopenAndWaitForReplicas() {
-               if ( !wfGetLB()->pingAll() ) {
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               $lb = $lbFactory->getMainLB();
+               if ( !$lb->pingAll() ) {
                        $this->output( "\n" );
                        do {
                                $this->error( "Connection failed, reconnecting in 10 seconds..." );
                                sleep( 10 );
-                       } while ( !wfGetLB()->pingAll() );
+                       } while ( !$lb->pingAll() );
                        $this->output( "Reconnected\n\n" );
                }
-               # Wait for the replica DB to catch up
-               wfWaitForSlaves();
+               // Wait for the replica DB to catch up
+               try {
+                       $lbFactory->waitForReplication();
+               } catch ( DBReplicationWaitError $e ) {
+                       // ignore
+               }
        }
 
        public function doSpecialPageCacheUpdates( $dbw ) {
index b9a7ccc..d6fd1b9 100644 (file)
@@ -6,10 +6,9 @@
     "doc": "jsduck",
     "postdoc": "grunt copy:jsduck",
     "selenium": "./tests/selenium/selenium.sh",
-    "selenium-test": "grunt webdriver:test"
+    "selenium-test": "wdio ./tests/selenium/wdio.conf.js"
   },
   "devDependencies": {
-    "bluebird": "3.5.1",
     "deepmerge": "1.3.2",
     "eslint": "4.9.0",
     "eslint-config-wikimedia": "0.5.0",
@@ -21,7 +20,6 @@
     "grunt-jsonlint": "1.1.0",
     "grunt-karma": "2.0.0",
     "grunt-stylelint": "0.10.0",
-    "grunt-webdriver": "2.0.3",
     "karma": "1.7.1",
     "karma-chrome-launcher": "2.2.0",
     "karma-firefox-launcher": "1.0.1",
index d3e1b65..e8a5e24 100644 (file)
@@ -113,6 +113,20 @@ return [
                ],
        ],
 
+       'jquery.tablesorter.styles' => [
+               'targets' => [ 'desktop', 'mobile' ],
+               'styles' => [
+                       'resources/src/jquery/jquery.tablesorter.styles.less',
+               ],
+       ],
+       'jquery.makeCollapsible.styles' => [
+               'targets' => [ 'desktop', 'mobile' ],
+               'class' => ResourceLoaderLessVarFileModule::class,
+               'styles' => [
+                       'resources/src/jquery/jquery.makeCollapsible.styles.less',
+               ],
+       ],
+
        'mediawiki.skinning.content.parsoid' => [
                // Style Parsoid HTML+RDFa output consistent with wikitext from PHP parser
                // with the interface.css styles; skinStyles should be used if your
@@ -277,6 +291,7 @@ return [
                'scripts' => 'resources/src/jquery/jquery.localize.js',
        ],
        'jquery.makeCollapsible' => [
+               'dependencies' => [ 'jquery.makeCollapsible.styles' ],
                'scripts' => 'resources/src/jquery/jquery.makeCollapsible.js',
                'styles' => 'resources/src/jquery/jquery.makeCollapsible.css',
                'messages' => [ 'collapsible-expand', 'collapsible-collapse' ],
@@ -317,6 +332,7 @@ return [
                'styles' => 'resources/src/jquery/jquery.tablesorter.less',
                'messages' => [ 'sort-descending', 'sort-ascending' ],
                'dependencies' => [
+                       'jquery.tablesorter.styles',
                        'mediawiki.RegExp',
                        'mediawiki.language.months',
                ],
@@ -712,7 +728,7 @@ return [
        'moment' => [
                'scripts' => [
                        // HACK: For some reason if you don't define window.moment first, loading moment fatals
-                       'resources/src/moment-global.js',
+                       'resources/src/moment/moment-global.js',
                        'resources/lib/moment/moment.js',
                ],
                'languageScripts' => [
@@ -739,7 +755,7 @@ return [
                        'de-ch' => 'resources/lib/moment/locale/de-ch.js',
                        'dv' => 'resources/lib/moment/locale/dv.js',
                        'el' => 'resources/lib/moment/locale/el.js',
-                       'en' => 'resources/src/moment-dmy.js',
+                       'en' => 'resources/src/moment/moment-dmy.js',
                        'en-au' => 'resources/lib/moment/locale/en-au.js',
                        'en-ca' => 'resources/lib/moment/locale/en-ca.js',
                        'en-gb' => 'resources/lib/moment/locale/en-gb.js',
@@ -827,7 +843,7 @@ return [
                // after locale definitions
                'skinScripts' => [
                        'default' => [
-                               'resources/src/moment-locale-overrides.js',
+                               'resources/src/moment/moment-locale-overrides.js',
                        ],
                ],
                'dependencies' => [
@@ -1197,11 +1213,6 @@ return [
                        'mediawiki.api',
                ],
        ],
-       'mediawiki.sectionAnchor' => [
-               // Back-compat to hide it on cached pages (T18691; Ie9e334e973; 2015-03-17)
-               'styles' => 'resources/src/mediawiki/mediawiki.sectionAnchor.css',
-               'targets' => [ 'desktop', 'mobile' ],
-       ],
        'mediawiki.storage' => [
                'scripts' => 'resources/src/mediawiki/mediawiki.storage.js',
                'targets' => [ 'desktop', 'mobile' ],
@@ -1616,7 +1627,10 @@ return [
        ],
 
        'mediawiki.libs.pluralruleparser' => [
-               'scripts' => 'resources/src/mediawiki.libs/CLDRPluralRuleParser.js',
+               'scripts' => [
+                       'resources/lib/CLDRPluralRuleParser/CLDRPluralRuleParser.js',
+                       'resources/src/mediawiki.libs.pluralruleparser/export.js',
+               ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
 
@@ -1656,7 +1670,10 @@ return [
        /* MediaWiki Libs */
 
        'mediawiki.libs.jpegmeta' => [
-               'scripts' => 'resources/src/mediawiki.libs/mediawiki.libs.jpegmeta.js',
+               'scripts' => [
+                       'resources/src/mediawiki.libs.jpegmeta/jpegmeta.js',
+                       'resources/src/mediawiki.libs.jpegmeta/export.js',
+               ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
 
@@ -2150,7 +2167,7 @@ return [
                'scripts' => [
                        'resources/src/mediawiki.special/mediawiki.special.preferences.confirmClose.js',
                        'resources/src/mediawiki.special/mediawiki.special.preferences.convertmessagebox.js',
-                       'resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js',
+                       'resources/src/mediawiki.special/mediawiki.special.preferences.tabs.legacy.js',
                        'resources/src/mediawiki.special/mediawiki.special.preferences.timezone.js',
                        'resources/src/mediawiki.special/mediawiki.special.preferences.personalEmail.js',
                ],
@@ -2167,6 +2184,35 @@ return [
                ],
        ],
        'mediawiki.special.preferences.styles' => [
+               'targets' => [ 'desktop', 'mobile' ],
+               'styles' => 'resources/src/mediawiki.special/mediawiki.special.preferences.styles.legacy.css',
+       ],
+       'mediawiki.special.preferences.ooui' => [
+               'targets' => [ 'desktop', 'mobile' ],
+               'scripts' => [
+                       'resources/src/mediawiki.special/mediawiki.special.preferences.confirmClose.js',
+                       'resources/src/mediawiki.special/mediawiki.special.preferences.convertmessagebox.js',
+                       'resources/src/mediawiki.special/mediawiki.special.preferences.editfont.js',
+                       'resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js',
+                       'resources/src/mediawiki.special/mediawiki.special.preferences.timezone.js',
+                       'resources/src/mediawiki.special/mediawiki.special.preferences.personalEmail.js',
+               ],
+               'messages' => [
+                       'prefs-tabs-navigation-hint',
+                       'prefswarning-warning',
+                       'saveprefs',
+                       'savedprefs',
+               ],
+               'dependencies' => [
+                       'mediawiki.language',
+                       'mediawiki.confirmCloseWindow',
+                       'mediawiki.notification.convertmessagebox',
+                       'oojs-ui-widgets',
+                       'mediawiki.widgets.SelectWithInputWidget',
+                       'mediawiki.editfont.styles',
+               ],
+       ],
+       'mediawiki.special.preferences.styles.ooui' => [
                'targets' => [ 'desktop', 'mobile' ],
                'styles' => 'resources/src/mediawiki.special/mediawiki.special.preferences.styles.css',
        ],
diff --git a/resources/lib/CLDRPluralRuleParser/CLDRPluralRuleParser.js b/resources/lib/CLDRPluralRuleParser/CLDRPluralRuleParser.js
new file mode 100644 (file)
index 0000000..1491e3d
--- /dev/null
@@ -0,0 +1,608 @@
+/**
+ * cldrpluralparser.js
+ * A parser engine for CLDR plural rules.
+ *
+ * Copyright 2012-2014 Santhosh Thottingal and other contributors
+ * Released under the MIT license
+ * http://opensource.org/licenses/MIT
+ *
+ * @version 0.1.0
+ * @source https://github.com/santhoshtr/CLDRPluralRuleParser
+ * @author Santhosh Thottingal <santhosh.thottingal@gmail.com>
+ * @author Timo Tijhof
+ * @author Amir Aharoni
+ */
+
+/**
+ * Evaluates a plural rule in CLDR syntax for a number
+ * @param {string} rule
+ * @param {integer} number
+ * @return {boolean} true if evaluation passed, false if evaluation failed.
+ */
+
+// UMD returnExports https://github.com/umdjs/umd/blob/master/returnExports.js
+(function(root, factory) {
+       if (typeof define === 'function' && define.amd) {
+               // AMD. Register as an anonymous module.
+               define(factory);
+       } else if (typeof exports === 'object') {
+               // Node. Does not work with strict CommonJS, but
+               // only CommonJS-like environments that support module.exports,
+               // like Node.
+               module.exports = factory();
+       } else {
+               // Browser globals (root is window)
+               root.pluralRuleParser = factory();
+       }
+}(this, function() {
+
+function pluralRuleParser(rule, number) {
+       'use strict';
+
+       /*
+       Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules
+       -----------------------------------------------------------------
+       condition     = and_condition ('or' and_condition)*
+               ('@integer' samples)?
+               ('@decimal' samples)?
+       and_condition = relation ('and' relation)*
+       relation      = is_relation | in_relation | within_relation
+       is_relation   = expr 'is' ('not')? value
+       in_relation   = expr (('not')? 'in' | '=' | '!=') range_list
+       within_relation = expr ('not')? 'within' range_list
+       expr          = operand (('mod' | '%') value)?
+       operand       = 'n' | 'i' | 'f' | 't' | 'v' | 'w'
+       range_list    = (range | value) (',' range_list)*
+       value         = digit+
+       digit         = 0|1|2|3|4|5|6|7|8|9
+       range         = value'..'value
+       samples       = sampleRange (',' sampleRange)* (',' ('…'|'...'))?
+       sampleRange   = decimalValue '~' decimalValue
+       decimalValue  = value ('.' value)?
+       */
+
+       // We don't evaluate the samples section of the rule. Ignore it.
+       rule = rule.split('@')[0].replace(/^\s*/, '').replace(/\s*$/, '');
+
+       if (!rule.length) {
+               // Empty rule or 'other' rule.
+               return true;
+       }
+
+       // Indicates the current position in the rule as we parse through it.
+       // Shared among all parsing functions below.
+       var pos = 0,
+               operand,
+               expression,
+               relation,
+               result,
+               whitespace = makeRegexParser(/^\s+/),
+               value = makeRegexParser(/^\d+/),
+               _n_ = makeStringParser('n'),
+               _i_ = makeStringParser('i'),
+               _f_ = makeStringParser('f'),
+               _t_ = makeStringParser('t'),
+               _v_ = makeStringParser('v'),
+               _w_ = makeStringParser('w'),
+               _is_ = makeStringParser('is'),
+               _isnot_ = makeStringParser('is not'),
+               _isnot_sign_ = makeStringParser('!='),
+               _equal_ = makeStringParser('='),
+               _mod_ = makeStringParser('mod'),
+               _percent_ = makeStringParser('%'),
+               _not_ = makeStringParser('not'),
+               _in_ = makeStringParser('in'),
+               _within_ = makeStringParser('within'),
+               _range_ = makeStringParser('..'),
+               _comma_ = makeStringParser(','),
+               _or_ = makeStringParser('or'),
+               _and_ = makeStringParser('and');
+
+       function debug() {
+               // console.log.apply(console, arguments);
+       }
+
+       debug('pluralRuleParser', rule, number);
+
+       // Try parsers until one works, if none work return null
+       function choice(parserSyntax) {
+               return function() {
+                       var i, result;
+
+                       for (i = 0; i < parserSyntax.length; i++) {
+                               result = parserSyntax[i]();
+
+                               if (result !== null) {
+                                       return result;
+                               }
+                       }
+
+                       return null;
+               };
+       }
+
+       // Try several parserSyntax-es in a row.
+       // All must succeed; otherwise, return null.
+       // This is the only eager one.
+       function sequence(parserSyntax) {
+               var i, parserRes,
+                       originalPos = pos,
+                       result = [];
+
+               for (i = 0; i < parserSyntax.length; i++) {
+                       parserRes = parserSyntax[i]();
+
+                       if (parserRes === null) {
+                               pos = originalPos;
+
+                               return null;
+                       }
+
+                       result.push(parserRes);
+               }
+
+               return result;
+       }
+
+       // Run the same parser over and over until it fails.
+       // Must succeed a minimum of n times; otherwise, return null.
+       function nOrMore(n, p) {
+               return function() {
+                       var originalPos = pos,
+                               result = [],
+                               parsed = p();
+
+                       while (parsed !== null) {
+                               result.push(parsed);
+                               parsed = p();
+                       }
+
+                       if (result.length < n) {
+                               pos = originalPos;
+
+                               return null;
+                       }
+
+                       return result;
+               };
+       }
+
+       // Helpers - just make parserSyntax out of simpler JS builtin types
+       function makeStringParser(s) {
+               var len = s.length;
+
+               return function() {
+                       var result = null;
+
+                       if (rule.substr(pos, len) === s) {
+                               result = s;
+                               pos += len;
+                       }
+
+                       return result;
+               };
+       }
+
+       function makeRegexParser(regex) {
+               return function() {
+                       var matches = rule.substr(pos).match(regex);
+
+                       if (matches === null) {
+                               return null;
+                       }
+
+                       pos += matches[0].length;
+
+                       return matches[0];
+               };
+       }
+
+       /**
+        * Integer digits of n.
+        */
+       function i() {
+               var result = _i_();
+
+               if (result === null) {
+                       debug(' -- failed i', parseInt(number, 10));
+
+                       return result;
+               }
+
+               result = parseInt(number, 10);
+               debug(' -- passed i ', result);
+
+               return result;
+       }
+
+       /**
+        * Absolute value of the source number (integer and decimals).
+        */
+       function n() {
+               var result = _n_();
+
+               if (result === null) {
+                       debug(' -- failed n ', number);
+
+                       return result;
+               }
+
+               result = parseFloat(number, 10);
+               debug(' -- passed n ', result);
+
+               return result;
+       }
+
+       /**
+        * Visible fractional digits in n, with trailing zeros.
+        */
+       function f() {
+               var result = _f_();
+
+               if (result === null) {
+                       debug(' -- failed f ', number);
+
+                       return result;
+               }
+
+               result = (number + '.').split('.')[1] || 0;
+               debug(' -- passed f ', result);
+
+               return result;
+       }
+
+       /**
+        * Visible fractional digits in n, without trailing zeros.
+        */
+       function t() {
+               var result = _t_();
+
+               if (result === null) {
+                       debug(' -- failed t ', number);
+
+                       return result;
+               }
+
+               result = (number + '.').split('.')[1].replace(/0$/, '') || 0;
+               debug(' -- passed t ', result);
+
+               return result;
+       }
+
+       /**
+        * Number of visible fraction digits in n, with trailing zeros.
+        */
+       function v() {
+               var result = _v_();
+
+               if (result === null) {
+                       debug(' -- failed v ', number);
+
+                       return result;
+               }
+
+               result = (number + '.').split('.')[1].length || 0;
+               debug(' -- passed v ', result);
+
+               return result;
+       }
+
+       /**
+        * Number of visible fraction digits in n, without trailing zeros.
+        */
+       function w() {
+               var result = _w_();
+
+               if (result === null) {
+                       debug(' -- failed w ', number);
+
+                       return result;
+               }
+
+               result = (number + '.').split('.')[1].replace(/0$/, '').length || 0;
+               debug(' -- passed w ', result);
+
+               return result;
+       }
+
+       // operand       = 'n' | 'i' | 'f' | 't' | 'v' | 'w'
+       operand = choice([n, i, f, t, v, w]);
+
+       // expr          = operand (('mod' | '%') value)?
+       expression = choice([mod, operand]);
+
+       function mod() {
+               var result = sequence(
+                       [operand, whitespace, choice([_mod_, _percent_]), whitespace, value]
+               );
+
+               if (result === null) {
+                       debug(' -- failed mod');
+
+                       return null;
+               }
+
+               debug(' -- passed ' + parseInt(result[0], 10) + ' ' + result[2] + ' ' + parseInt(result[4], 10));
+
+               return parseInt(result[0], 10) % parseInt(result[4], 10);
+       }
+
+       function not() {
+               var result = sequence([whitespace, _not_]);
+
+               if (result === null) {
+                       debug(' -- failed not');
+
+                       return null;
+               }
+
+               return result[1];
+       }
+
+       // is_relation   = expr 'is' ('not')? value
+       function is() {
+               var result = sequence([expression, whitespace, choice([_is_]), whitespace, value]);
+
+               if (result !== null) {
+                       debug(' -- passed is : ' + result[0] + ' == ' + parseInt(result[4], 10));
+
+                       return result[0] === parseInt(result[4], 10);
+               }
+
+               debug(' -- failed is');
+
+               return null;
+       }
+
+       // is_relation   = expr 'is' ('not')? value
+       function isnot() {
+               var result = sequence(
+                       [expression, whitespace, choice([_isnot_, _isnot_sign_]), whitespace, value]
+               );
+
+               if (result !== null) {
+                       debug(' -- passed isnot: ' + result[0] + ' != ' + parseInt(result[4], 10));
+
+                       return result[0] !== parseInt(result[4], 10);
+               }
+
+               debug(' -- failed isnot');
+
+               return null;
+       }
+
+       function not_in() {
+               var i, range_list,
+                       result = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]);
+
+               if (result !== null) {
+                       debug(' -- passed not_in: ' + result[0] + ' != ' + result[4]);
+                       range_list = result[4];
+
+                       for (i = 0; i < range_list.length; i++) {
+                               if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) {
+                                       return false;
+                               }
+                       }
+
+                       return true;
+               }
+
+               debug(' -- failed not_in');
+
+               return null;
+       }
+
+       // range_list    = (range | value) (',' range_list)*
+       function rangeList() {
+               var result = sequence([choice([range, value]), nOrMore(0, rangeTail)]),
+                       resultList = [];
+
+               if (result !== null) {
+                       resultList = resultList.concat(result[0]);
+
+                       if (result[1][0]) {
+                               resultList = resultList.concat(result[1][0]);
+                       }
+
+                       return resultList;
+               }
+
+               debug(' -- failed rangeList');
+
+               return null;
+       }
+
+       function rangeTail() {
+               // ',' range_list
+               var result = sequence([_comma_, rangeList]);
+
+               if (result !== null) {
+                       return result[1];
+               }
+
+               debug(' -- failed rangeTail');
+
+               return null;
+       }
+
+       // range         = value'..'value
+       function range() {
+               var i, array, left, right,
+                       result = sequence([value, _range_, value]);
+
+               if (result !== null) {
+                       debug(' -- passed range');
+
+                       array = [];
+                       left = parseInt(result[0], 10);
+                       right = parseInt(result[2], 10);
+
+                       for (i = left; i <= right; i++) {
+                               array.push(i);
+                       }
+
+                       return array;
+               }
+
+               debug(' -- failed range');
+
+               return null;
+       }
+
+       function _in() {
+               var result, range_list, i;
+
+               // in_relation   = expr ('not')? 'in' range_list
+               result = sequence(
+                       [expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList]
+               );
+
+               if (result !== null) {
+                       debug(' -- passed _in:' + result);
+
+                       range_list = result[5];
+
+                       for (i = 0; i < range_list.length; i++) {
+                               if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) {
+                                       return (result[1][0] !== 'not');
+                               }
+                       }
+
+                       return (result[1][0] === 'not');
+               }
+
+               debug(' -- failed _in ');
+
+               return null;
+       }
+
+       /**
+        * The difference between "in" and "within" is that
+        * "in" only includes integers in the specified range,
+        * while "within" includes all values.
+        */
+       function within() {
+               var range_list, result;
+
+               // within_relation = expr ('not')? 'within' range_list
+               result = sequence(
+                       [expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList]
+               );
+
+               if (result !== null) {
+                       debug(' -- passed within');
+
+                       range_list = result[5];
+
+                       if ((result[0] >= parseInt(range_list[0], 10)) &&
+                               (result[0] < parseInt(range_list[range_list.length - 1], 10))) {
+
+                               return (result[1][0] !== 'not');
+                       }
+
+                       return (result[1][0] === 'not');
+               }
+
+               debug(' -- failed within ');
+
+               return null;
+       }
+
+       // relation      = is_relation | in_relation | within_relation
+       relation = choice([is, not_in, isnot, _in, within]);
+
+       // and_condition = relation ('and' relation)*
+       function and() {
+               var i,
+                       result = sequence([relation, nOrMore(0, andTail)]);
+
+               if (result) {
+                       if (!result[0]) {
+                               return false;
+                       }
+
+                       for (i = 0; i < result[1].length; i++) {
+                               if (!result[1][i]) {
+                                       return false;
+                               }
+                       }
+
+                       return true;
+               }
+
+               debug(' -- failed and');
+
+               return null;
+       }
+
+       // ('and' relation)*
+       function andTail() {
+               var result = sequence([whitespace, _and_, whitespace, relation]);
+
+               if (result !== null) {
+                       debug(' -- passed andTail' + result);
+
+                       return result[3];
+               }
+
+               debug(' -- failed andTail');
+
+               return null;
+
+       }
+       //  ('or' and_condition)*
+       function orTail() {
+               var result = sequence([whitespace, _or_, whitespace, and]);
+
+               if (result !== null) {
+                       debug(' -- passed orTail: ' + result[3]);
+
+                       return result[3];
+               }
+
+               debug(' -- failed orTail');
+
+               return null;
+       }
+
+       // condition     = and_condition ('or' and_condition)*
+       function condition() {
+               var i,
+                       result = sequence([and, nOrMore(0, orTail)]);
+
+               if (result) {
+                       for (i = 0; i < result[1].length; i++) {
+                               if (result[1][i]) {
+                                       return true;
+                               }
+                       }
+
+                       return result[0];
+               }
+
+               return false;
+       }
+
+       result = condition();
+
+       /**
+        * For success, the pos must have gotten to the end of the rule
+        * and returned a non-null.
+        * n.b. This is part of language infrastructure,
+        * so we do not throw an internationalizable message.
+        */
+       if (result === null) {
+               throw new Error('Parse error at position ' + pos.toString() + ' for rule: ' + rule);
+       }
+
+       if (pos !== rule.length) {
+               debug('Warning: Rule not parsed completely. Parser stopped at ' + rule.substr(0, pos) + ' for rule: ' + rule);
+       }
+
+       return result;
+}
+
+return pluralRuleParser;
+
+}));
index 321b8b9..0450ec8 100644 (file)
@@ -16,6 +16,11 @@ jquery.ui.datepicker
 * I717f2580e Avoid deprecated jQuery.expr.filters.
 
 
+jquery.ui.mouse.js
+* Ia49ad6470 Avoid deprecated jQuery.fn.bind().
+* Ia49ad6470 Avoid deprecated jQuery.fn.unbind().
+
+
 jquery.ui.widget.js
 * I7ffbfd2e5 Avoid deprecated jQuery.expr[":"].
 * I717f2580e Avoid deprecated jQuery.fn.bind().
index 250365f..83d8e53 100644 (file)
@@ -29,10 +29,10 @@ $.widget("ui.mouse", {
                var that = this;
 
                this.element
-                       .bind('mousedown.'+this.widgetName, function(event) {
+                       .on('mousedown.'+this.widgetName, function(event) {
                                return that._mouseDown(event);
                        })
-                       .bind('click.'+this.widgetName, function(event) {
+                       .on('click.'+this.widgetName, function(event) {
                                if (true === $.data(event.target, that.widgetName + '.preventClickEvent')) {
                                        $.removeData(event.target, that.widgetName + '.preventClickEvent');
                                        event.stopImmediatePropagation();
@@ -46,11 +46,11 @@ $.widget("ui.mouse", {
        // TODO: make sure destroying one instance of mouse doesn't mess with
        // other instances of mouse
        _mouseDestroy: function() {
-               this.element.unbind('.'+this.widgetName);
+               this.element.off('.'+this.widgetName);
                if ( this._mouseMoveDelegate ) {
                        $(document)
-                               .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
-                               .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+                               .off('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+                               .off('mouseup.'+this.widgetName, this._mouseUpDelegate);
                }
        },
 
@@ -100,8 +100,8 @@ $.widget("ui.mouse", {
                        return that._mouseUp(event);
                };
                $(document)
-                       .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
-                       .bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+                       .on('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+                       .on('mouseup.'+this.widgetName, this._mouseUpDelegate);
 
                event.preventDefault();
 
@@ -131,8 +131,8 @@ $.widget("ui.mouse", {
 
        _mouseUp: function(event) {
                $(document)
-                       .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
-                       .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+                       .off('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+                       .off('mouseup.'+this.widgetName, this._mouseUpDelegate);
 
                if (this._mouseStarted) {
                        this._mouseStarted = false;
index 4749222..859544e 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * QUnit 2.4.0
+ * QUnit 2.6.0
  * https://qunitjs.com/
  *
  * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2017-07-08T15:20Z
+ * Date: 2018-03-27T02:18Z
  */
 
 /** Font Family and Sizes */
index bb8f31d..d7b22dd 100644 (file)
@@ -1,17 +1,17 @@
 /*!
- * QUnit 2.4.0
+ * QUnit 2.6.0
  * https://qunitjs.com/
  *
  * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2017-07-08T15:20Z
+ * Date: 2018-03-27T02:18Z
  */
 (function (global$1) {
   'use strict';
 
-  global$1 = global$1 && 'default' in global$1 ? global$1['default'] : global$1;
+  global$1 = global$1 && global$1.hasOwnProperty('default') ? global$1['default'] : global$1;
 
   var window = global$1.window;
   var self$1 = global$1.self;
   var priorityCount = 0;
   var unitSampler = void 0;
 
+  // This is a queue of functions that are tasks within a single test.
+  // After tests are dequeued from config.queue they are expanded into
+  // a set of tasks in this queue.
+  var taskQueue = [];
+
   /**
-   * Advances the ProcessingQueue to the next item if it is ready.
-   * @param {Boolean} last
+   * Advances the taskQueue to the next task. If the taskQueue is empty,
+   * process the testQueue
    */
   function advance() {
+       advanceTaskQueue();
+
+       if (!taskQueue.length) {
+               advanceTestQueue();
+       }
+  }
+
+  /**
+   * Advances the taskQueue to the next task if it is ready and not empty.
+   */
+  function advanceTaskQueue() {
        var start = now();
        config.depth = (config.depth || 0) + 1;
 
-       while (config.queue.length && !config.blocking) {
+       while (taskQueue.length && !config.blocking) {
                var elapsedTime = now() - start;
 
                if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
-                       if (priorityCount > 0) {
-                               priorityCount--;
-                       }
-
-                       config.queue.shift()();
+                       var task = taskQueue.shift();
+                       task();
                } else {
-                       setTimeout(advance, 13);
+                       setTimeout(advance);
                        break;
                }
        }
 
        config.depth--;
+  }
 
+  /**
+   * Advance the testQueue to the next test to process. Call done() if testQueue completes.
+   */
+  function advanceTestQueue() {
        if (!config.blocking && !config.queue.length && config.depth === 0) {
                done();
+               return;
        }
-  }
 
-  function addToQueueImmediate(callback) {
-       if (objectType(callback) === "array") {
-               while (callback.length) {
-                       addToQueueImmediate(callback.pop());
-               }
+       var testTasks = config.queue.shift();
+       addToTaskQueue(testTasks());
 
-               return;
+       if (priorityCount > 0) {
+               priorityCount--;
        }
 
-       config.queue.unshift(callback);
-       priorityCount++;
+       advance();
+  }
+
+  /**
+   * Enqueue the tasks for a test into the task queue.
+   * @param {Array} tasksArray
+   */
+  function addToTaskQueue(tasksArray) {
+       taskQueue.push.apply(taskQueue, toConsumableArray(tasksArray));
   }
 
   /**
-   * Adds a function to the ProcessingQueue for execution.
-   * @param {Function|Array} callback
-   * @param {Boolean} priority
+   * Return the number of tasks remaining in the task queue to be processed.
+   * @return {Number}
+   */
+  function taskQueueLength() {
+       return taskQueue.length;
+  }
+
+  /**
+   * Adds a test to the TestQueue for execution.
+   * @param {Function} testTasksFunc
+   * @param {Boolean} prioritize
    * @param {String} seed
    */
-  function addToQueue(callback, prioritize, seed) {
+  function addToTestQueue(testTasksFunc, prioritize, seed) {
        if (prioritize) {
-               config.queue.splice(priorityCount++, 0, callback);
+               config.queue.splice(priorityCount++, 0, testTasksFunc);
        } else if (seed) {
                if (!unitSampler) {
                        unitSampler = unitSamplerGenerator(seed);
 
                // Insert into a random position after all prioritized items
                var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1));
-               config.queue.splice(priorityCount + index, 0, callback);
+               config.queue.splice(priorityCount + index, 0, testTasksFunc);
        } else {
-               config.queue.push(callback);
+               config.queue.push(testTasksFunc);
        }
   }
 
        var runtime = now() - config.started;
        var passed = config.stats.all - config.stats.bad;
 
+       if (config.stats.all === 0) {
+
+               if (config.filter && config.filter.length) {
+                       throw new Error("No tests matched the filter \"" + config.filter + "\".");
+               }
+
+               if (config.module && config.module.length) {
+                       throw new Error("No tests matched the module \"" + config.module + "\".");
+               }
+
+               if (config.moduleId && config.moduleId.length) {
+                       throw new Error("No tests matched the moduleId \"" + config.moduleId + "\".");
+               }
+
+               if (config.testId && config.testId.length) {
+                       throw new Error("No tests matched the testId \"" + config.testId + "\".");
+               }
+
+               throw new Error("No tests were run.");
+       }
+
        emit("runEnd", globalSuite.end(true));
        runLoggingCallbacks("done", {
                passed: passed,
 
   var ProcessingQueue = {
        finished: false,
-       add: addToQueue,
-       addImmediate: addToQueueImmediate,
-       advance: advance
+       add: addToTestQueue,
+       advance: advance,
+       taskCount: taskQueueLength
   };
 
   var TestReport = function () {
                this.async = false;
                this.expected = 0;
        } else {
+               if (typeof this.callback !== "function") {
+                       var method = this.todo ? "todo" : "test";
+
+                       // eslint-disable-next-line max-len
+                       throw new TypeError("You must provide a function as a test callback to QUnit." + method + "(\"" + settings.testName + "\")");
+               }
+
                this.assert = new Assert(this);
        }
   }
                                _this.preserveEnvironment = true;
                        }
 
-                       if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && config.queue.length > 2) {
+                       // The 'after' hook should only execute when there are not tests left and
+                       // when the 'after' and 'finish' tasks are the only tasks left to process
+                       if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && (config.queue.length > 0 || ProcessingQueue.taskCount() > 2)) {
                                return;
                        }
 
 
        finish: function finish() {
                config.current = this;
+
+               if (this.steps.length) {
+                       var stepsList = this.steps.join(", ");
+                       this.pushFailure("Expected assert.verifySteps() to be called before end of test " + ("after using assert.step(). Unverified steps: " + stepsList), this.stack);
+               }
+
                if (config.requireExpects && this.expected === null) {
                        this.pushFailure("Expected number of assertions to be defined, but expect() was " + "not called.", this.stack);
                } else if (this.expected !== null && this.expected !== this.assertions.length) {
                }
 
                function runTest() {
-
-                       // Each of these can by async
-                       ProcessingQueue.addImmediate([function () {
+                       return [function () {
                                test.before();
-                       }, test.hooks("before"), function () {
+                       }].concat(toConsumableArray(test.hooks("before")), [function () {
                                test.preserveTestEnvironment();
-                       }, test.hooks("beforeEach"), function () {
+                       }], toConsumableArray(test.hooks("beforeEach")), [function () {
                                test.run();
-                       }, test.hooks("afterEach").reverse(), test.hooks("after").reverse(), function () {
+                       }], toConsumableArray(test.hooks("afterEach").reverse()), toConsumableArray(test.hooks("after").reverse()), [function () {
                                test.after();
                        }, function () {
                                test.finish();
 
        pushResult: function pushResult(resultInfo) {
                if (this !== config.current) {
-                       throw new Error("Assertion occured after test had finished.");
+                       throw new Error("Assertion occurred after test had finished.");
                }
 
                // Destructure of resultInfo = { result, actual, expected, message, negative }
                        result: resultInfo.result,
                        message: resultInfo.message,
                        actual: resultInfo.actual,
-                       expected: resultInfo.expected,
                        testId: this.testId,
                        negative: resultInfo.negative || false,
                        runtime: now() - this.started,
                        todo: !!this.todo
                };
 
+               if (hasOwn.call(resultInfo, "expected")) {
+                       details.expected = resultInfo.expected;
+               }
+
                if (!resultInfo.result) {
                        source = resultInfo.source || sourceFromStacktrace();
 
                        result: false,
                        message: message || "error",
                        actual: actual || null,
-                       expected: null,
                        source: source
                });
        },
                        then = promise.then;
                        if (objectType(then) === "function") {
                                resume = internalStop(test);
-                               then.call(promise, function () {
-                                       resume();
-                               }, function (error) {
-                                       message = "Promise rejected " + (!phase ? "during" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error);
-                                       test.pushFailure(message, extractStacktrace(error, 0));
-
-                                       // Else next test will carry the responsibility
-                                       saveGlobal();
-
-                                       // Unblock
-                                       resume();
-                               });
+                               if (config.notrycatch) {
+                                       then.call(promise, function () {
+                                               resume();
+                                       });
+                               } else {
+                                       then.call(promise, function () {
+                                               resume();
+                                       }, function (error) {
+                                               message = "Promise rejected " + (!phase ? "during" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error);
+                                               test.pushFailure(message, extractStacktrace(error, 0));
+
+                                               // Else next test will carry the responsibility
+                                               saveGlobal();
+
+                                               // Unblock
+                                               internalRecover(test);
+                                       });
+                               }
                        }
                }
        },
                        }
 
                        begin();
-               }, 13);
+               });
        } else {
                begin();
        }
        }, {
                key: "step",
                value: function step(message) {
+                       var assertionMessage = message;
                        var result = !!message;
 
                        this.test.steps.push(message);
 
+                       if (objectType(message) === "undefined" || message === "") {
+                               assertionMessage = "You must provide a message to assert.step";
+                       } else if (objectType(message) !== "string") {
+                               assertionMessage = "You must provide a string value to assert.step";
+                               result = false;
+                       }
+
                        return this.pushResult({
                                result: result,
-                               message: message || "You must provide a message to assert.step"
+                               message: assertionMessage
                        });
                }
 
        }, {
                key: "verifySteps",
                value: function verifySteps(steps, message) {
-                       this.deepEqual(this.test.steps, steps, message);
+
+                       // Since the steps array is just string values, we can clone with slice
+                       var actualStepsClone = this.test.steps.slice();
+                       this.deepEqual(actualStepsClone, steps, message);
+                       this.test.steps.length = 0;
                }
 
                // Specify the number of expected assertions to guarantee that failed test
                                message: message
                        });
                }
+       }, {
+               key: "rejects",
+               value: function rejects(promise, expected, message) {
+                       var result = false;
+
+                       var currentTest = this instanceof Assert && this.test || config.current;
+
+                       // 'expected' is optional unless doing string comparison
+                       if (objectType(expected) === "string") {
+                               if (message === undefined) {
+                                       message = expected;
+                                       expected = undefined;
+                               } else {
+                                       message = "assert.rejects does not accept a string value for the expected " + "argument.\nUse a non-string object value (e.g. validator function) instead " + "if necessary.";
+
+                                       currentTest.assert.pushResult({
+                                               result: false,
+                                               message: message
+                                       });
+
+                                       return;
+                               }
+                       }
+
+                       var then = promise && promise.then;
+                       if (objectType(then) !== "function") {
+                               var _message = "The value provided to `assert.rejects` in " + "\"" + currentTest.testName + "\" was not a promise.";
+
+                               currentTest.assert.pushResult({
+                                       result: false,
+                                       message: _message,
+                                       actual: promise
+                               });
+
+                               return;
+                       }
+
+                       var done = this.async();
+
+                       return then.call(promise, function handleFulfillment() {
+                               var message = "The promise returned by the `assert.rejects` callback in " + "\"" + currentTest.testName + "\" did not reject.";
+
+                               currentTest.assert.pushResult({
+                                       result: false,
+                                       message: message,
+                                       actual: promise
+                               });
+
+                               done();
+                       }, function handleRejection(actual) {
+                               var expectedType = objectType(expected);
+
+                               // We don't want to validate
+                               if (expected === undefined) {
+                                       result = true;
+                                       expected = actual;
+
+                                       // Expected is a regexp
+                               } else if (expectedType === "regexp") {
+                                       result = expected.test(errorString(actual));
+
+                                       // Expected is a constructor, maybe an Error constructor
+                               } else if (expectedType === "function" && actual instanceof expected) {
+                                       result = true;
+
+                                       // Expected is an Error object
+                               } else if (expectedType === "object") {
+                                       result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message;
+
+                                       // Expected is a validation function which returns true if validation passed
+                               } else {
+                                       if (expectedType === "function") {
+                                               result = expected.call({}, actual) === true;
+                                               expected = null;
+
+                                               // Expected is some other invalid type
+                                       } else {
+                                               result = false;
+                                               message = "invalid expected value provided to `assert.rejects` " + "callback in \"" + currentTest.testName + "\": " + expectedType + ".";
+                                       }
+                               }
+
+                               currentTest.assert.pushResult({
+                                       result: result,
+                                       actual: actual,
+                                       expected: expected,
+                                       message: message
+                               });
+
+                               done();
+                       });
+               }
        }]);
        return Assert;
   }();
        return false;
   }
 
+  // Handle an unhandled rejection
+  function onUnhandledRejection(reason) {
+       var resultInfo = {
+               result: false,
+               message: reason.message || "error",
+               actual: reason,
+               source: reason.stack || sourceFromStacktrace(3)
+       };
+
+       var currentTest = config.current;
+       if (currentTest) {
+               currentTest.assert.pushResult(resultInfo);
+       } else {
+               test("global failure", extend(function (assert) {
+                       assert.pushResult(resultInfo);
+               }, { validTest: true }));
+       }
+  }
+
   var focused = false;
   var QUnit = {};
   var globalSuite = new SuiteReport();
   QUnit.isLocal = !(defined.document && window.location.protocol !== "file:");
 
   // Expose the current QUnit version
-  QUnit.version = "2.4.0";
+  QUnit.version = "2.6.0";
 
   function createModule(name, testEnvironment, modifiers) {
        var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null;
                return sourceFromStacktrace(offset);
        },
 
-       onError: onError
+       onError: onError,
+
+       onUnhandledRejection: onUnhandledRejection
   });
 
   QUnit.pushFailure = pushFailure;
        if (defined.setTimeout) {
                setTimeout(function () {
                        begin();
-               }, 13);
+               });
        } else {
                begin();
        }
 
                var fixture = document.getElementById("qunit-fixture");
                if (fixture) {
-                       config.fixture = fixture.innerHTML;
+                       config.fixture = fixture.cloneNode(true);
                }
        }
 
                }
 
                var fixture = document.getElementById("qunit-fixture");
-               if (fixture) {
-                       fixture.innerHTML = config.fixture;
+               var resetFixtureType = _typeof(config.fixture);
+               if (resetFixtureType === "string") {
+
+                       // support user defined values for `config.fixture`
+                       var newFixture = document.createElement("div");
+                       newFixture.setAttribute("id", "qunit-fixture");
+                       newFixture.innerHTML = config.fixture;
+                       fixture.parentNode.replaceChild(newFixture, fixture);
+               } else {
+                       var clonedFixture = config.fixture.cloneNode(true);
+                       fixture.parentNode.replaceChild(clonedFixture, fixture);
                }
        }
 
                        // Skip inherited or undefined properties
                        if (hasOwn.call(params, key) && params[key] !== undefined) {
 
-                               // Output a parameter for each value of this key (but usually just one)
+                               // Output a parameter for each value of this key
+                               // (but usually just one)
                                arrValue = [].concat(params[key]);
                                for (i = 0; i < arrValue.length; i++) {
                                        querystring += encodeURIComponent(key);
                if (config.altertitle && document$$1.title) {
 
                        // Show ✖ for good, ✔ for bad suite result in title
-                       // use escape sequences in case file gets loaded with non-utf-8-charset
+                       // use escape sequences in case file gets loaded with non-utf-8
+                       // charset
                        document$$1.title = [stats.failedTests ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" ");
                }
 
                if (running) {
                        bad = QUnit.config.reorder && details.previousFailure;
 
-                       running.innerHTML = (bad ? "Rerunning previously failed test: <br />" : "Running: <br />") + getNameHtml(details.name, details.module);
+                       running.innerHTML = [bad ? "Rerunning previously failed test: <br />" : "Running: <br />", getNameHtml(details.name, details.module)].join("");
                }
        });
 
 
                return ret;
        };
+
+       // Listen for unhandled rejections, and call QUnit.onUnhandledRejection
+       window.addEventListener("unhandledrejection", function (event) {
+               QUnit.onUnhandledRejection(event.reason);
+       });
   })();
 
   /*
                                line = text.substring(lineStart, lineEnd + 1);
                                lineStart = lineEnd + 1;
 
-                               if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined) {
+                               var lineHashExists = lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined;
+
+                               if (lineHashExists) {
                                        chars += String.fromCharCode(lineHash[line]);
                                } else {
                                        chars += String.fromCharCode(lineArrayLength);
index 6364c70..fc52d51 100644 (file)
@@ -1,3 +1,9 @@
+/*
+ * Please do not add any CSS rules here that impact the positioning of the element
+ *  e.g. padding, margin, position or float.
+ * These instead should live in jquery.makeCollapsible.styles
+*/
+
 /* See also jquery.makeCollapsible.js */
 .mw-collapsible-toggle {
        float: right;
 .mw-collapsible-toggle-default:after {
        content: ']';
 }
-/* Align the toggle based on the direction of the content language */
-/* @noflip */
-.mw-content-ltr .mw-collapsible-toggle,
-.mw-content-rtl .mw-content-ltr .mw-collapsible-toggle {
-       float: right;
-}
-/* @noflip */
-.mw-content-rtl .mw-collapsible-toggle,
-.mw-content-ltr .mw-content-rtl .mw-collapsible-toggle {
-       float: left;
-}
 
 .mw-customtoggle,
 .mw-collapsible-toggle {
@@ -37,17 +32,3 @@ caption .mw-collapsible-toggle,
 .mw-content-ltr .mw-content-rtl caption .mw-collapsible-toggle {
        float: none;
 }
-
-/* list-items go as wide as their parent element, don't float them inside list items */
-li .mw-collapsible-toggle,
-.mw-content-ltr li .mw-collapsible-toggle,
-.mw-content-rtl li .mw-collapsible-toggle,
-.mw-content-rtl .mw-content-ltr li .mw-collapsible-toggle,
-.mw-content-ltr .mw-content-rtl li .mw-collapsible-toggle {
-       float: none;
-}
-
-/* the added list item should have no list-style */
-.mw-collapsible-toggle-li {
-       list-style: none;
-}
index 1f40e0a..e355196 100644 (file)
@@ -1,5 +1,8 @@
 /**
  * jQuery makeCollapsible
+ * Note: To avoid performance issues such as reflows, several styles are
+ * shipped in mediawiki.makeCollapsible.styles to reserve space for the toggle control. Please
+ * familiarise yourself with that CSS before making any changes to this code.
  *
  * Dual licensed:
  * - CC BY 3.0 <http://creativecommons.org/licenses/by/3.0>
         *   expand the element. Default: the 'data-expandtext' attribute of the
         *   collapsible element or the content of 'collapsible-expand' message.
         * @param {boolean} [options.collapsed] Whether to collapse immediately. By default
-        *   collapse only if the elements has the 'mw-collapsible' class.
+        *   collapse only if the element has the 'mw-collapsed' class.
         * @param {jQuery} [options.$customTogglers] Elements to be used as togglers
         *   for this collapsible element. By default, if the collapsible element
         *   has an id attribute like 'mw-customcollapsible-XXX', elements with a
                        if ( $collapsible.data( 'mw-made-collapsible' ) ) {
                                return;
                        } else {
-                               $collapsible.data( 'mw-made-collapsible', true );
+                               // Let CSS know that it no longer needs to worry about flash of unstyled content.
+                               // This will allow mediawiki.makeCollapsible.styles to disable temporary pseudo elements, that
+                               // are needed to avoid a flash of unstyled content.
+                               $collapsible.addClass( 'mw-made-collapsible' )
+                                       .data( 'mw-made-collapsible', true );
                        }
 
                        // Use custom text or default?
diff --git a/resources/src/jquery/jquery.makeCollapsible.styles.less b/resources/src/jquery/jquery.makeCollapsible.styles.less
new file mode 100644 (file)
index 0000000..f19c3c2
--- /dev/null
@@ -0,0 +1,125 @@
+/**
+ * These rules prevent re-flows relating to collapsible on-wiki elements (T42812).
+ * This is done by temporarily creating a pseudo element in the place that JavaScript will insert
+ * a toggle control. The same CSS rules that control the positioning of the toggle control will apply
+ * to the pseudo element. When the JavaScript has executed
+ * (See corresponding non-render blocking CSS in jquery.makeCollapsible)
+ * all pseudo elements will be removed.
+ *
+ * Currently we support all the examples on [[mw:Manual:Collapsible_elements/Demo/Simple]]
+ * All examples on [[mw:Manual:Collapsible_elements/Demo/Advanced]] are supported with the following
+ * exceptions
+ * - Custom collapsible 4 (table-row)
+ * -- CSS selector would be too complicated
+ * - Collapsible div nested in collapsed div
+ * -- Given it's not easy to identify the collapsed content via CSS, text will be shown until
+ *    JavaScript has loaded
+ * - "Combination example"
+ * -- At a later time, we may want to support the use of of `attr`, but given the code
+ *    complexity we will not for the time being (see https://davidwalsh.name/css-content-attr)
+ */
+
+// This selector is used frequently in the code to indicate that the JavaScript has successfully completed
+// it's execution and pseudo elements can be disabled. For readability and maintainability it is separated
+// as a LESS variable.
+@exclude: ~'.mw-made-collapsible';
+
+.client-js {
+
+       ol.mw-collapsible:before,
+       ul.mw-collapsible:before,
+       .mw-collapsible-toggle-li {
+               /*
+               Rather than inherit any margins from the the general li selector - make sure this is explicit
+               to avoid reflows
+               */
+               display: list-item;
+               list-style: none;
+               margin-bottom: 0.1em;
+       }
+
+       // Reset when mw-collapsible-toggle-li is rendered
+       ol.mw-made-collapsible:before,
+       ul.mw-made-collapsible:before {
+               display: none;
+       }
+
+       ol.mw-collapsible:not( @{exclude} ):before,
+       ul.mw-collapsible:not( @{exclude} ):before,
+       // Where the tbody or thead is the first child of the collapsible table
+       table.mw-collapsible:not( @{exclude} ) :first-child tr:first-child th:last-child:before,
+       table.mw-collapsible:not( @{exclude} ) > caption:first-child:after {
+               content: '[@{msg-collapsible-collapse}]';
+       }
+
+       td.mw-collapsed:not( @{exclude} ):before,
+       table.mw-collapsed:not( @{exclude} ) :first-child tr:first-child th:last-child:before,
+       table.mw-collapsed:not( @{exclude} ) > caption:first-child:after,
+       div.mw-collapsed:not( @{exclude} ):before {
+               content: '[@{msg-collapsible-expand}]';
+       }
+
+       // Any element with id beginning `mw-customcollapsible` will have special treatment
+       .mw-collapsible[ id^='mw-customcollapsible' ] th:before,
+       .mw-collapsible[ id^='mw-customcollapsible' ]:before {
+               content: none !important; // stylelint-disable-line declaration-no-important
+       }
+
+       // Special case for table where first child is caption element
+       table.mw-collapsible:not( @{exclude} ) > caption:first-child:after {
+               float: none;
+               display: block;
+       }
+
+       // Use the exclude selector to ensure animations do not break
+       .mw-collapsed:not( @{exclude} ) {
+               // Avoid FOUC/reflows on collapsed elements by making sure they are opened by default (T42812)
+               > p,
+               > table,
+               // Manual:Collapsible_elements/Demo/Simple#Collapsed_by_default
+               > thead + tbody,
+               tr:not( :first-child ),
+               .mw-collapsible-content {
+                       display: none;
+               }
+       }
+}
+
+/* Align the toggle based on the direction of the content language */
+/* @noflip */
+.mw-content-ltr,
+.mw-content-rtl .mw-content-ltr {
+       .mw-collapsible:not( @{exclude} ) th:before,
+       .mw-collapsible:not( @{exclude} ):before,
+       .mw-collapsible-toggle {
+               float: right;
+       }
+}
+
+/* @noflip */
+.mw-content-rtl,
+.mw-content-ltr .mw-content-rtl {
+       .mw-collapsible:not( @{exclude} ) th:before,
+       .mw-collapsible:not( @{exclude} ):before,
+       .mw-collapsible-toggle {
+               float: left;
+       }
+}
+
+/* list-items go as wide as their parent element, don't float them inside list items */
+li,
+.mw-content-ltr li,
+.mw-content-rtl li,
+.mw-content-ltr .mw-content-rtl li,
+.mw-content-rtl .mw-content-ltr li {
+       .mw-collapsible-toggle {
+               float: none;
+       }
+}
+
+// special treatment for list items to match above
+// !important necessary to override overly-specific float left and right above.
+ol.mw-collapsible:not( @{exclude} ):before,
+ul.mw-collapsible:not( @{exclude} ):before {
+       float: none !important; // stylelint-disable-line declaration-no-important
+}
index e9d5a91..552c0c3 100644 (file)
                                                return;
                                        }
                                }
-                               $table.addClass( 'jquery-tablesorter' );
+                               // The `sortable` class is used to identify tables which will become sortable
+                               // If not used it will create a FOUC but it should be added since the sortable class
+                               // is responsible for certain crucial style elements. If the class is already present
+                               // this action will be harmless.
+                               $table.addClass( 'jquery-tablesorter sortable' );
 
                                // Merge and extend
                                config = $.extend( {}, $.tablesorter.defaultOptions, settings );
index ce24b0d..3bea471 100644 (file)
@@ -8,7 +8,10 @@ table.jquery-tablesorter {
                cursor: pointer;
                background-repeat: no-repeat;
                background-position: center right;
-               padding-right: 21px;
+               // Note: To avoid reflows, a padding is set in
+               // the jquery.tableSorter.styles module as a render blocking style.
+               // Please do not add any CSS rules here that impact the positioning of the element
+               // e.g. padding, margin, position or float.
        }
 
        th.headerSortUp {
diff --git a/resources/src/jquery/jquery.tablesorter.styles.less b/resources/src/jquery/jquery.tablesorter.styles.less
new file mode 100644 (file)
index 0000000..bd6b5dd
--- /dev/null
@@ -0,0 +1,6 @@
+.client-js {
+       // Reserve space for table sortable controls
+       table.sortable th {
+               padding-right: 21px;
+       }
+}
index a751cf0..db81ff2 100644 (file)
@@ -58,7 +58,8 @@
                border-radius: 0 0 2px 2px;
        }
 
-       .editButtons .oo-ui-buttonInputWidget,
+       // Use buttonElement to include ButtonInputWidget and ButtonWidget
+       .editButtons .oo-ui-buttonElement,
        .cancelLink,
        .editHelp {
                margin-top: 0.5em;
index 2572b52..b95a436 100644 (file)
@@ -215,6 +215,7 @@ table.toc td {
 }
 
 /* preference page with js-genrated toc */
+/* TODO: Delete #preftoc when Special:Preference's non-OOUI mode is disabled */
 #preftoc {
        float: left;
        margin: 1em 1em 1em 1em;
index f58f039..27d049e 100644 (file)
        window.importScript = importScript;
        window.importStylesheet = importStylesheet;
 
+       /**
+        * Replace document.write/writeln with basic html parsing that appends
+        * to the <body> to avoid blanking pages. Added JavaScript will not run.
+        *
+        * @deprecated since 1.26
+        */
+       [ 'write', 'writeln' ].forEach( function ( method ) {
+               mw.log.deprecate( document, method, function () {
+                       $( 'body' ).append( $.parseHTML( Array.prototype.join.call( arguments, '' ) ) );
+               }, 'Use jQuery or mw.loader.load instead.', 'document.' + method );
+       } );
+
 }( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki.libs.jpegmeta/export.js b/resources/src/mediawiki.libs.jpegmeta/export.js
new file mode 100644 (file)
index 0000000..e8913aa
--- /dev/null
@@ -0,0 +1,12 @@
+/* global JpegMeta */
+( function ( mw ) {
+
+       // Export as module
+       module.exports = function ( fileReaderResult, fileName ) {
+               return new JpegMeta.JpegFile( fileReaderResult, fileName );
+       };
+
+       // Back-compat: Also expose via mw.lib
+       // @deprecated since 1.31
+       mw.log.deprecate( mw.libs, 'jpegmeta', module.exports );
+}( mediaWiki ) );
diff --git a/resources/src/mediawiki.libs.jpegmeta/jpegmeta.js b/resources/src/mediawiki.libs.jpegmeta/jpegmeta.js
new file mode 100644 (file)
index 0000000..ed85914
--- /dev/null
@@ -0,0 +1,731 @@
+/**
+ * This is JsJpegMeta v1.0
+ * From: https://code.google.com/p/jsjpegmeta/downloads/list
+ * From: https://github.com/bennoleslie/jsjpegmeta/blob/v1.0.0/jpegmeta.js
+ *
+ * Ported to MediaWiki ResourceLoader by Bryan Tong Minh
+ * Changes:
+ * - Add closure.
+ * - Add this.JpegMeta assignment to expose it as global.
+ */
+
+( function () {
+       /*
+       Copyright (c) 2009 Ben Leslie
+       
+       Permission is hereby granted, free of charge, to any person obtaining a copy
+       of this software and associated documentation files (the "Software"), to deal
+       in the Software without restriction, including without limitation the rights
+       to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+       copies of the Software, and to permit persons to whom the Software is
+       furnished to do so, subject to the following conditions:
+       
+       The above copyright notice and this permission notice shall be included in
+       all copies or substantial portions of the Software.
+       
+       THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+       IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+       FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+       AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+       LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+       OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+       THE SOFTWARE.
+       */
+       
+       /*
+        This JavaScript library is used to parse meta-data from files 
+        with mime-type image/jpeg.
+       
+        Include it with something like:
+       
+          <script type="text/javascript" src="jpegmeta.js"></script>
+       
+        This adds a single 'module' object called 'JpegMeta' to the global
+        namespace.
+       
+        Public Functions
+        ----------------
+        JpegMeta.parseNum - parse unsigned integers from binary data
+        JpegMeta.parseSnum - parse signed integers from binary data
+       
+        Public Classes
+        --------------
+        JpegMeta.Rational - A rational number class
+        JpegMeta.JfifSegment
+        JpegMeta.ExifSegment
+        JpegMeta.JpegFile - Primary class for Javascript parsing
+       */
+
+       var JpegMeta = {};
+       // MediaWiki: Expose as global
+       this.JpegMeta = JpegMeta;
+       
+       /* 
+          parse an unsigned number of size bytes at offset in some binary string data.
+          If endian
+          is "<" parse the data as little endian, if endian
+          is ">" parse as big-endian.
+       */
+       JpegMeta.parseNum = function parseNum(endian, data, offset, size) {
+           var i;
+           var ret;
+           var big_endian = (endian === ">");
+           if (offset === undefined) offset = 0;
+           if (size === undefined) size = data.length - offset;
+           for (big_endian ? i = offset : i = offset + size - 1; 
+                big_endian ? i < offset + size : i >= offset; 
+                big_endian ? i++ : i--) {
+               ret <<= 8;
+               ret += data.charCodeAt(i);
+           }
+           return ret;
+       };
+       
+       /* 
+          parse an signed number of size bytes at offset in some binary string data.
+          If endian
+          is "<" parse the data as little endian, if endian
+          is ">" parse as big-endian.
+       */
+       JpegMeta.parseSnum = function parseSnum(endian, data, offset, size) {
+           var i;
+           var ret;
+           var neg;
+           var big_endian = (endian === ">");
+           if (offset === undefined) offset = 0;
+           if (size === undefined) size = data.length - offset;
+           for (big_endian ? i = offset : i = offset + size - 1; 
+                big_endian ? i < offset + size : i >= offset; 
+                big_endian ? i++ : i--) {
+               if (neg === undefined) {
+                   /* Negative if top bit is set */
+                   neg = (data.charCodeAt(i) & 0x80) === 0x80;
+               }
+               ret <<= 8;
+               /* If it is negative we invert the bits */
+               ret += neg ? ~data.charCodeAt(i) & 0xff: data.charCodeAt(i);
+           }
+           if (neg) {
+               /* If it is negative we do two's complement */
+               ret += 1;
+               ret *= -1;
+           }
+           return ret;
+       };
+       
+       /* Rational number class */
+       JpegMeta.Rational = function Rational(num, den)
+       {
+           this.num = num;
+           this.den = den || 1;
+           return this;
+       };
+       
+       /* Rational number methods */
+       JpegMeta.Rational.prototype.toString = function toString() {
+           if (this.num === 0) {
+               return "" + this.num;
+           }
+           if (this.den === 1) {
+               return "" + this.num;
+           }
+           if (this.num === 1) {
+               return this.num + " / " + this.den;
+           }
+           return this.num / this.den; // + "/" + this.den;
+       };
+       
+       JpegMeta.Rational.prototype.asFloat = function asFloat() {
+           return this.num / this.den;
+       };
+       
+       /* MetaGroup class */
+       JpegMeta.MetaGroup = function MetaGroup(fieldName, description) {
+           this.fieldName = fieldName;
+           this.description = description;
+           this.metaProps = {};
+           return this;
+       };
+       
+       JpegMeta.MetaGroup.prototype._addProperty = function _addProperty(fieldName, description, value) {
+           var property = new JpegMeta.MetaProp(fieldName, description, value);
+           this[property.fieldName] = property;
+           this.metaProps[property.fieldName] = property;
+       };
+       
+       JpegMeta.MetaGroup.prototype.toString = function toString() {
+           return "[MetaGroup " + this.description + "]";
+       };
+
+       /* MetaProp class */
+       JpegMeta.MetaProp = function MetaProp(fieldName, description, value) {
+           this.fieldName = fieldName;
+           this.description = description;
+           this.value = value;
+           return this;
+       };
+       
+       JpegMeta.MetaProp.prototype.toString = function toString() {
+           return "" + this.value;
+       };
+
+       /* JpegFile class */
+       JpegMeta.JpegFile = function JpegFile(binary_data, filename) {
+           /* Change this to EOI if we want to parse. */
+           var break_segment = this._SOS;
+           
+           this.metaGroups = {};
+           this._binary_data = binary_data;
+           this.filename = filename;
+           
+           /* Go through and parse. */
+           var pos = 0;
+           var pos_start_of_segment = 0;
+           var delim;
+           var mark;
+           var _mark;
+           var segsize;
+           var headersize;
+           var mark_code;
+           var mark_fn;
+       
+           /* Check to see if this looks like a JPEG file */
+           if (this._binary_data.slice(0, 2) !== this._SOI_MARKER) {
+               throw new Error("Doesn't look like a JPEG file. First two bytes are " + 
+                               this._binary_data.charCodeAt(0) + "," + 
+                               this._binary_data.charCodeAt(1) + ".");
+           }
+           
+           pos += 2;
+           
+           while (pos < this._binary_data.length) {
+               delim = this._binary_data.charCodeAt(pos++);
+               mark = this._binary_data.charCodeAt(pos++);
+               
+               pos_start_of_segment = pos;
+               
+               if (delim != this._DELIM) {
+                   break;
+               }
+               
+               if (mark === break_segment) {
+                   break;
+               }
+               
+               headersize = JpegMeta.parseNum(">", this._binary_data, pos, 2);
+               
+               /* Find the end */
+               pos += headersize;
+               while (pos < this._binary_data.length) {
+                   delim = this._binary_data.charCodeAt(pos++);
+                   if (delim == this._DELIM) {
+                       _mark = this._binary_data.charCodeAt(pos++);
+                       if (_mark != 0x0) {
+                           pos -= 2;
+                           break;
+                       }
+                   }
+               }
+               
+               segsize = pos - pos_start_of_segment;
+               
+               if (this._markers[mark]) {
+                   mark_code = this._markers[mark][0];
+                   mark_fn = this._markers[mark][1];
+               } else {
+                   mark_code = "UNKN";
+                   mark_fn = undefined;
+               }
+               
+               if (mark_fn) {
+                   this[mark_fn](mark, pos_start_of_segment + 2);
+               }
+               
+           }
+           
+           if (this.general === undefined) {
+               throw Error("Invalid JPEG file.");
+           }
+           
+           return this;
+       };
+       
+       this.JpegMeta.JpegFile.prototype.toString = function () {
+           return "[JpegFile " + this.filename + " " + 
+               this.general.type + " " + 
+               this.general.pixelWidth + "x" + 
+               this.general.pixelHeight +
+               " Depth: " + this.general.depth + "]";
+       };
+       
+       /* Some useful constants */
+       this.JpegMeta.JpegFile.prototype._SOI_MARKER = '\xff\xd8';
+       this.JpegMeta.JpegFile.prototype._DELIM = 0xff;
+       this.JpegMeta.JpegFile.prototype._EOI = 0xd9;
+       this.JpegMeta.JpegFile.prototype._SOS = 0xda;
+       
+       this.JpegMeta.JpegFile.prototype._sofHandler = function _sofHandler (mark, pos) {
+           if (this.general !== undefined) {
+               throw Error("Unexpected multiple-frame image");
+           }
+       
+           this._addMetaGroup("general", "General");
+           this.general._addProperty("depth", "Depth", JpegMeta.parseNum(">", this._binary_data, pos, 1));
+           this.general._addProperty("pixelHeight", "Pixel Height", JpegMeta.parseNum(">", this._binary_data, pos + 1, 2));
+           this.general._addProperty("pixelWidth", "Pixel Width",JpegMeta.parseNum(">", this._binary_data, pos + 3, 2));
+           this.general._addProperty("type", "Type", this._markers[mark][2]);
+       };
+       
+       /* JFIF idents */
+       this.JpegMeta.JpegFile.prototype._JFIF_IDENT = "JFIF\x00";
+       this.JpegMeta.JpegFile.prototype._JFXX_IDENT = "JFXX\x00";
+       
+       /* Exif idents */
+       this.JpegMeta.JpegFile.prototype._EXIF_IDENT = "Exif\x00";
+       
+       /* TIFF types */
+       this.JpegMeta.JpegFile.prototype._types = {
+           /* The format is identifier : ["type name", type_size_in_bytes ] */
+           1 : ["BYTE", 1],
+           2 : ["ASCII", 1],
+           3 : ["SHORT", 2],
+           4 : ["LONG", 4],
+           5 : ["RATIONAL", 8],
+           6 : ["SBYTE", 1],
+           7 : ["UNDEFINED", 1],
+           8 : ["SSHORT", 2],
+           9 : ["SLONG", 4],
+           10 : ["SRATIONAL", 8],
+           11 : ["FLOAT", 4],
+           12 : ["DOUBLE", 8]
+       };
+       
+       this.JpegMeta.JpegFile.prototype._tifftags = {
+           /* A. Tags relating to image data structure */
+           256 : ["Image width", "ImageWidth"],
+           257 : ["Image height", "ImageLength"],
+           258 : ["Number of bits per component", "BitsPerSample"],
+           259 : ["Compression scheme", "Compression", 
+                  {1 : "uncompressed", 6 : "JPEG compression" }],
+           262 : ["Pixel composition", "PhotmetricInerpretation",
+                  {2 : "RGB", 6 : "YCbCr"}],
+           274 : ["Orientation of image", "Orientation",
+                  /* FIXME: Check the mirror-image / reverse encoding and rotation */
+                  {1 : "Normal", 2 : "Reverse?", 
+                   3 : "Upside-down", 4 : "Upside-down Reverse",
+                   5 : "90 degree CW", 6 : "90 degree CW reverse",
+                   7 : "90 degree CCW", 8 : "90 degree CCW reverse"}],
+           277 : ["Number of components", "SamplesPerPixel"],
+           284 : ["Image data arrangement", "PlanarConfiguration",
+                  {1 : "chunky format", 2 : "planar format"}],
+           530 : ["Subsampling ratio of Y to C", "YCbCrSubSampling"],
+           531 : ["Y and C positioning", "YCbCrPositioning",
+                  {1 : "centered", 2 : "co-sited"}],
+           282 : ["X Resolution", "XResolution"],
+           283 : ["Y Resolution", "YResolution"],
+           296 : ["Resolution Unit", "ResolutionUnit",
+                  {2 : "inches", 3 : "centimeters"}],
+           /* B. Tags realting to recording offset */
+           273 : ["Image data location", "StripOffsets"],
+           278 : ["Number of rows per strip", "RowsPerStrip"],
+           279 : ["Bytes per compressed strip", "StripByteCounts"],
+           513 : ["Offset to JPEG SOI", "JPEGInterchangeFormat"],
+           514 : ["Bytes of JPEG Data", "JPEGInterchangeFormatLength"],
+           /* C. Tags relating to image data characteristics */
+           301 : ["Transfer function", "TransferFunction"],
+           318 : ["White point chromaticity", "WhitePoint"],
+           319 : ["Chromaticities of primaries", "PrimaryChromaticities"],
+           529 : ["Color space transformation matrix coefficients", "YCbCrCoefficients"],
+           532 : ["Pair of black and white reference values", "ReferenceBlackWhite"],
+           /* D. Other tags */
+           306 : ["Date and time", "DateTime"],
+           270 : ["Image title", "ImageDescription"],
+           271 : ["Make", "Make"],
+           272 : ["Model", "Model"],
+           305 : ["Software", "Software"],
+           315 : ["Person who created the image", "Artist"],
+           316 : ["Host Computer", "HostComputer"],
+           33432 : ["Copyright holder", "Copyright"],
+           
+           34665 : ["Exif tag", "ExifIfdPointer"],
+           34853 : ["GPS tag", "GPSInfoIfdPointer"]
+       };
+       
+       this.JpegMeta.JpegFile.prototype._exiftags = {
+           /* Tag Support Levels (2) - 0th IFX Exif Private Tags */
+           /* A. Tags Relating to Version */
+           36864 : ["Exif Version", "ExifVersion"],
+           40960 : ["FlashPix Version", "FlashpixVersion"],
+           
+           /* B. Tag Relating to Image Data Characteristics */
+           40961 : ["Color Space", "ColorSpace"],
+           
+           /* C. Tags Relating to Image Configuration */
+           37121 : ["Meaning of each component", "ComponentsConfiguration"],
+           37122 : ["Compressed Bits Per Pixel", "CompressedBitsPerPixel"],
+           40962 : ["Pixel X Dimension", "PixelXDimension"],
+           40963 : ["Pixel Y Dimension", "PixelYDimension"],
+           
+           /* D. Tags Relating to User Information */
+           37500 : ["Manufacturer notes", "MakerNote"],
+           37510 : ["User comments", "UserComment"],
+           
+           /* E. Tag Relating to Related File Information */
+           40964 : ["Related audio file", "RelatedSoundFile"],
+           
+           /* F. Tags Relating to Date and Time */
+           36867 : ["Date Time Original", "DateTimeOriginal"],
+           36868 : ["Date Time Digitized", "DateTimeDigitized"],
+           37520 : ["DateTime subseconds", "SubSecTime"],
+           37521 : ["DateTimeOriginal subseconds", "SubSecTimeOriginal"],
+           37522 : ["DateTimeDigitized subseconds", "SubSecTimeDigitized"],
+           
+           /* G. Tags Relating to Picture-Taking Conditions */
+           33434 : ["Exposure time", "ExposureTime"],
+           33437 : ["FNumber", "FNumber"],
+           34850 : ["Exposure program", "ExposureProgram"],
+           34852 : ["Spectral sensitivity", "SpectralSensitivity"],
+           34855 : ["ISO Speed Ratings", "ISOSpeedRatings"],
+           34856 : ["Optoelectric coefficient", "OECF"],
+           37377 : ["Shutter Speed",  "ShutterSpeedValue"],
+           37378 : ["Aperture Value", "ApertureValue"],
+           37379 : ["Brightness", "BrightnessValue"],
+           37380 : ["Exposure Bias Value", "ExposureBiasValue"],
+           37381 : ["Max Aperture Value", "MaxApertureValue"],
+           37382 : ["Subject Distance", "SubjectDistance"],
+           37383 : ["Metering Mode", "MeteringMode"],
+           37384 : ["Light Source", "LightSource"],
+           37385 : ["Flash", "Flash"],
+           37386 : ["Focal Length", "FocalLength"],
+           37396 : ["Subject Area", "SubjectArea"],
+           41483 : ["Flash Energy", "FlashEnergy"],
+           41484 : ["Spatial Frequency Response", "SpatialFrequencyResponse"],
+           41486 : ["Focal Plane X Resolution", "FocalPlaneXResolution"],
+           41487 : ["Focal Plane Y Resolution", "FocalPlaneYResolution"],
+           41488 : ["Focal Plane Resolution Unit", "FocalPlaneResolutionUnit"],
+           41492 : ["Subject Location", "SubjectLocation"],
+           41493 : ["Exposure Index", "ExposureIndex"],
+           41495 : ["Sensing Method", "SensingMethod"],
+           41728 : ["File Source", "FileSource"],
+           41729 : ["Scene Type", "SceneType"],
+           41730 : ["CFA Pattern", "CFAPattern"],
+           41985 : ["Custom Rendered", "CustomRendered"],
+           41986 : ["Exposure Mode", "Exposure Mode"],
+           41987 : ["White Balance", "WhiteBalance"],
+           41988 : ["Digital Zoom Ratio", "DigitalZoomRatio"],
+           41990 : ["Scene Capture Type", "SceneCaptureType"],
+           41991 : ["Gain Control", "GainControl"],
+           41992 : ["Contrast", "Contrast"],
+           41993 : ["Saturation", "Saturation"],
+           41994 : ["Sharpness", "Sharpness"],
+           41995 : ["Device settings description", "DeviceSettingDescription"],
+           41996 : ["Subject distance range", "SubjectDistanceRange"],
+           
+           /* H. Other Tags */
+           42016 : ["Unique image ID", "ImageUniqueID"],
+           
+           40965 : ["Interoperability tag", "InteroperabilityIFDPointer"]
+       };
+       
+       this.JpegMeta.JpegFile.prototype._gpstags = {
+           /* A. Tags Relating to GPS */
+           0 : ["GPS tag version", "GPSVersionID"],
+           1 : ["North or South Latitude", "GPSLatitudeRef"],
+           2 : ["Latitude", "GPSLatitude"],
+           3 : ["East or West Longitude", "GPSLongitudeRef"],
+           4 : ["Longitude", "GPSLongitude"],
+           5 : ["Altitude reference", "GPSAltitudeRef"],
+           6 : ["Altitude", "GPSAltitude"],
+           7 : ["GPS time (atomic clock)", "GPSTimeStamp"],
+           8 : ["GPS satellites usedd for measurement", "GPSSatellites"],
+           9 : ["GPS receiver status", "GPSStatus"],
+           10 : ["GPS mesaurement mode", "GPSMeasureMode"],
+           11 : ["Measurement precision", "GPSDOP"],
+           12 : ["Speed unit", "GPSSpeedRef"],
+           13 : ["Speed of GPS receiver", "GPSSpeed"],
+           14 : ["Reference for direction of movement", "GPSTrackRef"],
+           15 : ["Direction of movement", "GPSTrack"],
+           16 : ["Reference for direction of image", "GPSImgDirectionRef"],
+           17 : ["Direction of image", "GPSImgDirection"],
+           18 : ["Geodetic survey data used", "GPSMapDatum"],
+           19 : ["Reference for latitude of destination", "GPSDestLatitudeRef"],
+           20 : ["Latitude of destination", "GPSDestLatitude"],
+           21 : ["Reference for longitude of destination", "GPSDestLongitudeRef"],
+           22 : ["Longitude of destination", "GPSDestLongitude"],
+           23 : ["Reference for bearing of destination", "GPSDestBearingRef"],
+           24 : ["Bearing of destination", "GPSDestBearing"],
+           25 : ["Reference for distance to destination", "GPSDestDistanceRef"],
+           26 : ["Distance to destination", "GPSDestDistance"],
+           27 : ["Name of GPS processing method", "GPSProcessingMethod"],
+           28 : ["Name of GPS area", "GPSAreaInformation"],
+           29 : ["GPS Date", "GPSDateStamp"],
+           30 : ["GPS differential correction", "GPSDifferential"]
+       };
+
+       this.JpegMeta.JpegFile.prototype._markers = {
+           /* Start Of Frame markers, non-differential, Huffman coding */
+           0xc0: ["SOF0", "_sofHandler", "Baseline DCT"],
+           0xc1: ["SOF1", "_sofHandler", "Extended sequential DCT"],
+           0xc2: ["SOF2", "_sofHandler", "Progressive DCT"],
+           0xc3: ["SOF3", "_sofHandler", "Lossless (sequential)"],
+           
+           /* Start Of Frame markers, differential, Huffman coding */
+           0xc5: ["SOF5", "_sofHandler", "Differential sequential DCT"],
+           0xc6: ["SOF6", "_sofHandler", "Differential progressive DCT"],
+           0xc7: ["SOF7", "_sofHandler", "Differential lossless (sequential)"],
+           
+           /* Start Of Frame markers, non-differential, arithmetic coding */
+           0xc8: ["JPG", null, "Reserved for JPEG extensions"],
+           0xc9: ["SOF9", "_sofHandler", "Extended sequential DCT"],
+           0xca: ["SOF10", "_sofHandler", "Progressive DCT"],
+           0xcb: ["SOF11", "_sofHandler", "Lossless (sequential)"],
+           
+           /* Start Of Frame markers, differential, arithmetic coding */
+           0xcd: ["SOF13", "_sofHandler", "Differential sequential DCT"],
+           0xce: ["SOF14", "_sofHandler", "Differential progressive DCT"],
+           0xcf: ["SOF15", "_sofHandler", "Differential lossless (sequential)"],
+           
+           /* Huffman table specification */
+           0xc4: ["DHT", null, "Define Huffman table(s)"],
+           0xcc: ["DAC", null, "Define arithmetic coding conditioning(s)"],
+           
+           /* Restart interval termination" */
+           0xd0: ["RST0", null, "Restart with modulo 8 count “0”"],
+           0xd1: ["RST1", null, "Restart with modulo 8 count “1”"],
+           0xd2: ["RST2", null, "Restart with modulo 8 count “2”"],
+           0xd3: ["RST3", null, "Restart with modulo 8 count “3”"],
+           0xd4: ["RST4", null, "Restart with modulo 8 count “4”"],
+           0xd5: ["RST5", null, "Restart with modulo 8 count “5”"],
+           0xd6: ["RST6", null, "Restart with modulo 8 count “6”"],
+           0xd7: ["RST7", null, "Restart with modulo 8 count “7”"],
+           
+           /* Other markers */
+           0xd8: ["SOI", null, "Start of image"],
+           0xd9: ["EOI", null, "End of image"],
+           0xda: ["SOS", null, "Start of scan"],
+           0xdb: ["DQT", null, "Define quantization table(s)"],
+           0xdc: ["DNL", null, "Define number of lines"],
+           0xdd: ["DRI", null, "Define restart interval"],
+           0xde: ["DHP", null, "Define hierarchical progression"],
+           0xdf: ["EXP", null, "Expand reference component(s)"],
+           0xe0: ["APP0", "_app0Handler", "Reserved for application segments"],
+           0xe1: ["APP1", "_app1Handler"],
+           0xe2: ["APP2", null],
+           0xe3: ["APP3", null],
+           0xe4: ["APP4", null],
+           0xe5: ["APP5", null],
+           0xe6: ["APP6", null],
+           0xe7: ["APP7", null],
+           0xe8: ["APP8", null],
+           0xe9: ["APP9", null],
+           0xea: ["APP10", null],
+           0xeb: ["APP11", null],
+           0xec: ["APP12", null],
+           0xed: ["APP13", null],
+           0xee: ["APP14", null],
+           0xef: ["APP15", null],
+           0xf0: ["JPG0", null], /* Reserved for JPEG extensions */
+           0xf1: ["JPG1", null],
+           0xf2: ["JPG2", null],
+           0xf3: ["JPG3", null],
+           0xf4: ["JPG4", null],
+           0xf5: ["JPG5", null],
+           0xf6: ["JPG6", null],
+           0xf7: ["JPG7", null],
+           0xf8: ["JPG8", null],
+           0xf9: ["JPG9", null],
+           0xfa: ["JPG10", null],
+           0xfb: ["JPG11", null],
+           0xfc: ["JPG12", null],
+           0xfd: ["JPG13", null],
+           0xfe: ["COM", null], /* Comment */
+           
+           /* Reserved markers */
+           0x01: ["JPG13", null] /* For temporary private use in arithmetic coding */
+           /* 02 -> bf are reserverd */
+       };
+
+       /* Private methods */
+       this.JpegMeta.JpegFile.prototype._addMetaGroup = function _addMetaGroup(name, description) {
+           var group = new JpegMeta.MetaGroup(name, description);
+           this[group.fieldName] = group;
+           this.metaGroups[group.fieldName] = group;
+           return group;
+       };
+
+       this.JpegMeta.JpegFile.prototype._parseIfd = function _parseIfd(endian, _binary_data, base, ifd_offset, tags, name, description) {
+           var num_fields = JpegMeta.parseNum(endian, _binary_data, base + ifd_offset, 2);
+           /* Per tag variables */
+           var i, j;
+           var tag_base;
+           var tag_field;
+           var type, type_field, type_size;
+           var num_values;
+           var value_offset;
+           var value;
+           var _val;
+           var num;
+           var den;
+           
+           var group;
+           
+           group = this._addMetaGroup(name, description);
+       
+           for (var i = 0; i < num_fields; i++) {
+               /* parse the field */
+               tag_base = base + ifd_offset + 2 + (i * 12);
+               tag_field = JpegMeta.parseNum(endian, _binary_data, tag_base, 2);
+               type_field = JpegMeta.parseNum(endian, _binary_data, tag_base + 2, 2);
+               num_values = JpegMeta.parseNum(endian, _binary_data, tag_base + 4, 4);
+               value_offset = JpegMeta.parseNum(endian, _binary_data, tag_base + 8, 4);
+               if (this._types[type_field] === undefined) {
+                   continue;
+               }
+               type = this._types[type_field][0];
+               type_size = this._types[type_field][1];
+               
+               if (type_size * num_values <= 4) {
+                   /* Data is in-line */
+                   value_offset = tag_base + 8;
+               } else {
+                   value_offset = base + value_offset;
+               }
+               
+               /* Read the value */
+               if (type == "UNDEFINED") {
+                   value = _binary_data.slice(value_offset, value_offset + num_values);
+               } else if (type == "ASCII") {
+                   value = _binary_data.slice(value_offset, value_offset + num_values);
+                   value = value.split('\x00')[0];
+                   /* strip trail nul */
+               } else {
+                   value = new Array();
+                   for (j = 0; j < num_values; j++, value_offset += type_size) {
+                       if (type == "BYTE" || type == "SHORT" || type == "LONG") {
+                           value.push(JpegMeta.parseNum(endian, _binary_data, value_offset, type_size));
+                       }
+                       if (type == "SBYTE" || type == "SSHORT" || type == "SLONG") {
+                           value.push(JpegMeta.parseSnum(endian, _binary_data, value_offset, type_size));
+                       }
+                       if (type == "RATIONAL") {
+                           num = JpegMeta.parseNum(endian, _binary_data, value_offset, 4);
+                           den = JpegMeta.parseNum(endian, _binary_data, value_offset + 4, 4);
+                           value.push(new JpegMeta.Rational(num, den));
+                       }
+                       if (type == "SRATIONAL") {
+                           num = JpegMeta.parseSnum(endian, _binary_data, value_offset, 4);
+                           den = JpegMeta.parseSnum(endian, _binary_data, value_offset + 4, 4);
+                           value.push(new JpegMeta.Rational(num, den));
+                       }
+                       value.push();
+                   }
+                   if (num_values === 1) {
+                       value = value[0];
+                   }
+               }
+               if (tags[tag_field] !== undefined) {
+                       group._addProperty(tags[tag_field][1], tags[tag_field][0], value);
+               }
+           }
+       };
+
+       this.JpegMeta.JpegFile.prototype._jfifHandler = function _jfifHandler(mark, pos) {
+           if (this.jfif !== undefined) {
+               throw Error("Multiple JFIF segments found");
+           }
+           this._addMetaGroup("jfif", "JFIF");
+           this.jfif._addProperty("version_major", "Version Major", this._binary_data.charCodeAt(pos + 5));
+           this.jfif._addProperty("version_minor", "Version Minor", this._binary_data.charCodeAt(pos + 6));
+           this.jfif._addProperty("version", "JFIF Version", this.jfif.version_major.value + "." + this.jfif.version_minor.value);
+           this.jfif._addProperty("units", "Density Unit", this._binary_data.charCodeAt(pos + 7));
+           this.jfif._addProperty("Xdensity", "X density", JpegMeta.parseNum(">", this._binary_data, pos + 8, 2));
+           this.jfif._addProperty("Ydensity", "Y Density", JpegMeta.parseNum(">", this._binary_data, pos + 10, 2));
+           this.jfif._addProperty("Xthumbnail", "X Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 12, 1));
+           this.jfif._addProperty("Ythumbnail", "Y Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 13, 1));
+       };
+
+       /* Handle app0 segments */
+       this.JpegMeta.JpegFile.prototype._app0Handler = function app0Handler(mark, pos) {
+           var ident = this._binary_data.slice(pos, pos + 5);
+           if (ident == this._JFIF_IDENT) {
+               this._jfifHandler(mark, pos);
+           } else if (ident == this._JFXX_IDENT) {
+               /* Don't handle JFXX Ident yet */
+           } else {
+               /* Don't know about other idents */
+           }
+       };
+
+       /* Handle app1 segments */
+       this.JpegMeta.JpegFile.prototype._app1Handler = function _app1Handler(mark, pos) {
+           var ident = this._binary_data.slice(pos, pos + 5);
+           if (ident == this._EXIF_IDENT) {
+               this._exifHandler(mark, pos + 6);
+           } else {
+               /* Don't know about other idents */
+           }
+       };
+
+       /* Handle exif segments */
+       JpegMeta.JpegFile.prototype._exifHandler = function _exifHandler(mark, pos) {
+           if (this.exif !== undefined) {
+               throw new Error("Multiple JFIF segments found");
+           }
+           
+           /* Parse this TIFF header */
+           var endian;
+           var magic_field;
+           var ifd_offset;
+           var primary_ifd, exif_ifd, gps_ifd;
+           var endian_field = this._binary_data.slice(pos, pos + 2);
+           
+           /* Trivia: This 'I' is for Intel, the 'M' is for Motorola */
+           if (endian_field === "II") {
+               endian = "<";
+           } else if (endian_field === "MM") {
+               endian = ">";
+           } else {
+               throw new Error("Malformed TIFF meta-data. Unknown endianess: " + endian_field);
+           }
+           
+           magic_field = JpegMeta.parseNum(endian, this._binary_data, pos + 2, 2);
+           
+           if (magic_field !== 42) {
+               throw new Error("Malformed TIFF meta-data. Bad magic: " + magic_field);
+           }
+           
+           ifd_offset = JpegMeta.parseNum(endian, this._binary_data, pos + 4, 4);
+           
+           /* Parse 0th IFD */
+           this._parseIfd(endian, this._binary_data, pos, ifd_offset, this._tifftags, "tiff", "TIFF");
+           
+           if (this.tiff.ExifIfdPointer) {
+               this._parseIfd(endian, this._binary_data, pos, this.tiff.ExifIfdPointer.value, this._exiftags, "exif", "Exif");
+           }
+           
+           if (this.tiff.GPSInfoIfdPointer) {
+               this._parseIfd(endian, this._binary_data, pos, this.tiff.GPSInfoIfdPointer.value, this._gpstags, "gps", "GPS");
+               if (this.gps.GPSLatitude) {
+                   var latitude;
+                   latitude = this.gps.GPSLatitude.value[0].asFloat() + 
+                       (1 / 60) * this.gps.GPSLatitude.value[1].asFloat() + 
+                       (1 / 3600) * this.gps.GPSLatitude.value[2].asFloat();
+                   if (this.gps.GPSLatitudeRef.value === "S") {
+                       latitude = -latitude;
+                   }
+                   this.gps._addProperty("latitude", "Dec. Latitude", latitude);
+               }
+               if (this.gps.GPSLongitude) {
+                   var longitude;
+                   longitude = this.gps.GPSLongitude.value[0].asFloat() + 
+                       (1 / 60) * this.gps.GPSLongitude.value[1].asFloat() + 
+                       (1 / 3600) * this.gps.GPSLongitude.value[2].asFloat();
+                   if (this.gps.GPSLongitudeRef.value === "W") {
+                       longitude = -longitude;
+                   }
+                   this.gps._addProperty("longitude", "Dec. Longitude", longitude);
+               }
+           }
+       };
+
+}() );
diff --git a/resources/src/mediawiki.libs.pluralruleparser/export.js b/resources/src/mediawiki.libs.pluralruleparser/export.js
new file mode 100644 (file)
index 0000000..28449d3
--- /dev/null
@@ -0,0 +1,5 @@
+// Expose via module.exports
+module.exports = window.pluralRuleParser;
+
+// Back-compat: Also expose via mw.lib
+mediaWiki.libs.pluralRuleParser = window.pluralRuleParser;
diff --git a/resources/src/mediawiki.libs/CLDRPluralRuleParser.js b/resources/src/mediawiki.libs/CLDRPluralRuleParser.js
deleted file mode 100644 (file)
index 549a9ab..0000000
+++ /dev/null
@@ -1,596 +0,0 @@
-/* This is CLDRPluralRuleParser v1.1.3, ported to MediaWiki ResourceLoader */
-
-/**
-* CLDRPluralRuleParser.js
-* A parser engine for CLDR plural rules.
-*
-* Copyright 2012-2014 Santhosh Thottingal and other contributors
-* Released under the MIT license
-* http://opensource.org/licenses/MIT
-*
-* @source https://github.com/santhoshtr/CLDRPluralRuleParser
-* @author Santhosh Thottingal <santhosh.thottingal@gmail.com>
-* @author Timo Tijhof
-* @author Amir Aharoni
-*/
-
-( function ( mw ) {
-/**
- * Evaluates a plural rule in CLDR syntax for a number
- * @param {string} rule
- * @param {integer} number
- * @return {boolean} true if evaluation passed, false if evaluation failed.
- */
-
-function pluralRuleParser(rule, number) {
-       'use strict';
-
-       /*
-       Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules
-       -----------------------------------------------------------------
-       condition     = and_condition ('or' and_condition)*
-               ('@integer' samples)?
-               ('@decimal' samples)?
-       and_condition = relation ('and' relation)*
-       relation      = is_relation | in_relation | within_relation
-       is_relation   = expr 'is' ('not')? value
-       in_relation   = expr (('not')? 'in' | '=' | '!=') range_list
-       within_relation = expr ('not')? 'within' range_list
-       expr          = operand (('mod' | '%') value)?
-       operand       = 'n' | 'i' | 'f' | 't' | 'v' | 'w'
-       range_list    = (range | value) (',' range_list)*
-       value         = digit+
-       digit         = 0|1|2|3|4|5|6|7|8|9
-       range         = value'..'value
-       samples       = sampleRange (',' sampleRange)* (',' ('…'|'...'))?
-       sampleRange   = decimalValue '~' decimalValue
-       decimalValue  = value ('.' value)?
-       */
-
-       // We don't evaluate the samples section of the rule. Ignore it.
-       rule = rule.split('@')[0].replace(/^\s*/, '').replace(/\s*$/, '');
-
-       if (!rule.length) {
-               // Empty rule or 'other' rule.
-               return true;
-       }
-
-       // Indicates the current position in the rule as we parse through it.
-       // Shared among all parsing functions below.
-       var pos = 0,
-               operand,
-               expression,
-               relation,
-               result,
-               whitespace = makeRegexParser(/^\s+/),
-               value = makeRegexParser(/^\d+/),
-               _n_ = makeStringParser('n'),
-               _i_ = makeStringParser('i'),
-               _f_ = makeStringParser('f'),
-               _t_ = makeStringParser('t'),
-               _v_ = makeStringParser('v'),
-               _w_ = makeStringParser('w'),
-               _is_ = makeStringParser('is'),
-               _isnot_ = makeStringParser('is not'),
-               _isnot_sign_ = makeStringParser('!='),
-               _equal_ = makeStringParser('='),
-               _mod_ = makeStringParser('mod'),
-               _percent_ = makeStringParser('%'),
-               _not_ = makeStringParser('not'),
-               _in_ = makeStringParser('in'),
-               _within_ = makeStringParser('within'),
-               _range_ = makeStringParser('..'),
-               _comma_ = makeStringParser(','),
-               _or_ = makeStringParser('or'),
-               _and_ = makeStringParser('and');
-
-       function debug() {
-               // console.log.apply(console, arguments);
-       }
-
-       debug('pluralRuleParser', rule, number);
-
-       // Try parsers until one works, if none work return null
-       function choice(parserSyntax) {
-               return function() {
-                       var i, result;
-
-                       for (i = 0; i < parserSyntax.length; i++) {
-                               result = parserSyntax[i]();
-
-                               if (result !== null) {
-                                       return result;
-                               }
-                       }
-
-                       return null;
-               };
-       }
-
-       // Try several parserSyntax-es in a row.
-       // All must succeed; otherwise, return null.
-       // This is the only eager one.
-       function sequence(parserSyntax) {
-               var i, parserRes,
-                       originalPos = pos,
-                       result = [];
-
-               for (i = 0; i < parserSyntax.length; i++) {
-                       parserRes = parserSyntax[i]();
-
-                       if (parserRes === null) {
-                               pos = originalPos;
-
-                               return null;
-                       }
-
-                       result.push(parserRes);
-               }
-
-               return result;
-       }
-
-       // Run the same parser over and over until it fails.
-       // Must succeed a minimum of n times; otherwise, return null.
-       function nOrMore(n, p) {
-               return function() {
-                       var originalPos = pos,
-                               result = [],
-                               parsed = p();
-
-                       while (parsed !== null) {
-                               result.push(parsed);
-                               parsed = p();
-                       }
-
-                       if (result.length < n) {
-                               pos = originalPos;
-
-                               return null;
-                       }
-
-                       return result;
-               };
-       }
-
-       // Helpers - just make parserSyntax out of simpler JS builtin types
-       function makeStringParser(s) {
-               var len = s.length;
-
-               return function() {
-                       var result = null;
-
-                       if (rule.substr(pos, len) === s) {
-                               result = s;
-                               pos += len;
-                       }
-
-                       return result;
-               };
-       }
-
-       function makeRegexParser(regex) {
-               return function() {
-                       var matches = rule.substr(pos).match(regex);
-
-                       if (matches === null) {
-                               return null;
-                       }
-
-                       pos += matches[0].length;
-
-                       return matches[0];
-               };
-       }
-
-       /**
-        * Integer digits of n.
-        */
-       function i() {
-               var result = _i_();
-
-               if (result === null) {
-                       debug(' -- failed i', parseInt(number, 10));
-
-                       return result;
-               }
-
-               result = parseInt(number, 10);
-               debug(' -- passed i ', result);
-
-               return result;
-       }
-
-       /**
-        * Absolute value of the source number (integer and decimals).
-        */
-       function n() {
-               var result = _n_();
-
-               if (result === null) {
-                       debug(' -- failed n ', number);
-
-                       return result;
-               }
-
-               result = parseFloat(number, 10);
-               debug(' -- passed n ', result);
-
-               return result;
-       }
-
-       /**
-        * Visible fractional digits in n, with trailing zeros.
-        */
-       function f() {
-               var result = _f_();
-
-               if (result === null) {
-                       debug(' -- failed f ', number);
-
-                       return result;
-               }
-
-               result = (number + '.').split('.')[1] || 0;
-               debug(' -- passed f ', result);
-
-               return result;
-       }
-
-       /**
-        * Visible fractional digits in n, without trailing zeros.
-        */
-       function t() {
-               var result = _t_();
-
-               if (result === null) {
-                       debug(' -- failed t ', number);
-
-                       return result;
-               }
-
-               result = (number + '.').split('.')[1].replace(/0$/, '') || 0;
-               debug(' -- passed t ', result);
-
-               return result;
-       }
-
-       /**
-        * Number of visible fraction digits in n, with trailing zeros.
-        */
-       function v() {
-               var result = _v_();
-
-               if (result === null) {
-                       debug(' -- failed v ', number);
-
-                       return result;
-               }
-
-               result = (number + '.').split('.')[1].length || 0;
-               debug(' -- passed v ', result);
-
-               return result;
-       }
-
-       /**
-        * Number of visible fraction digits in n, without trailing zeros.
-        */
-       function w() {
-               var result = _w_();
-
-               if (result === null) {
-                       debug(' -- failed w ', number);
-
-                       return result;
-               }
-
-               result = (number + '.').split('.')[1].replace(/0$/, '').length || 0;
-               debug(' -- passed w ', result);
-
-               return result;
-       }
-
-       // operand       = 'n' | 'i' | 'f' | 't' | 'v' | 'w'
-       operand = choice([n, i, f, t, v, w]);
-
-       // expr          = operand (('mod' | '%') value)?
-       expression = choice([mod, operand]);
-
-       function mod() {
-               var result = sequence(
-                       [operand, whitespace, choice([_mod_, _percent_]), whitespace, value]
-               );
-
-               if (result === null) {
-                       debug(' -- failed mod');
-
-                       return null;
-               }
-
-               debug(' -- passed ' + parseInt(result[0], 10) + ' ' + result[2] + ' ' + parseInt(result[4], 10));
-
-               return parseInt(result[0], 10) % parseInt(result[4], 10);
-       }
-
-       function not() {
-               var result = sequence([whitespace, _not_]);
-
-               if (result === null) {
-                       debug(' -- failed not');
-
-                       return null;
-               }
-
-               return result[1];
-       }
-
-       // is_relation   = expr 'is' ('not')? value
-       function is() {
-               var result = sequence([expression, whitespace, choice([_is_]), whitespace, value]);
-
-               if (result !== null) {
-                       debug(' -- passed is : ' + result[0] + ' == ' + parseInt(result[4], 10));
-
-                       return result[0] === parseInt(result[4], 10);
-               }
-
-               debug(' -- failed is');
-
-               return null;
-       }
-
-       // is_relation   = expr 'is' ('not')? value
-       function isnot() {
-               var result = sequence(
-                       [expression, whitespace, choice([_isnot_, _isnot_sign_]), whitespace, value]
-               );
-
-               if (result !== null) {
-                       debug(' -- passed isnot: ' + result[0] + ' != ' + parseInt(result[4], 10));
-
-                       return result[0] !== parseInt(result[4], 10);
-               }
-
-               debug(' -- failed isnot');
-
-               return null;
-       }
-
-       function not_in() {
-               var i, range_list,
-                       result = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]);
-
-               if (result !== null) {
-                       debug(' -- passed not_in: ' + result[0] + ' != ' + result[4]);
-                       range_list = result[4];
-
-                       for (i = 0; i < range_list.length; i++) {
-                               if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) {
-                                       return false;
-                               }
-                       }
-
-                       return true;
-               }
-
-               debug(' -- failed not_in');
-
-               return null;
-       }
-
-       // range_list    = (range | value) (',' range_list)*
-       function rangeList() {
-               var result = sequence([choice([range, value]), nOrMore(0, rangeTail)]),
-                       resultList = [];
-
-               if (result !== null) {
-                       resultList = resultList.concat(result[0]);
-
-                       if (result[1][0]) {
-                               resultList = resultList.concat(result[1][0]);
-                       }
-
-                       return resultList;
-               }
-
-               debug(' -- failed rangeList');
-
-               return null;
-       }
-
-       function rangeTail() {
-               // ',' range_list
-               var result = sequence([_comma_, rangeList]);
-
-               if (result !== null) {
-                       return result[1];
-               }
-
-               debug(' -- failed rangeTail');
-
-               return null;
-       }
-
-       // range         = value'..'value
-       function range() {
-               var i, array, left, right,
-                       result = sequence([value, _range_, value]);
-
-               if (result !== null) {
-                       debug(' -- passed range');
-
-                       array = [];
-                       left = parseInt(result[0], 10);
-                       right = parseInt(result[2], 10);
-
-                       for (i = left; i <= right; i++) {
-                               array.push(i);
-                       }
-
-                       return array;
-               }
-
-               debug(' -- failed range');
-
-               return null;
-       }
-
-       function _in() {
-               var result, range_list, i;
-
-               // in_relation   = expr ('not')? 'in' range_list
-               result = sequence(
-                       [expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList]
-               );
-
-               if (result !== null) {
-                       debug(' -- passed _in:' + result);
-
-                       range_list = result[5];
-
-                       for (i = 0; i < range_list.length; i++) {
-                               if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) {
-                                       return (result[1][0] !== 'not');
-                               }
-                       }
-
-                       return (result[1][0] === 'not');
-               }
-
-               debug(' -- failed _in ');
-
-               return null;
-       }
-
-       /**
-        * The difference between "in" and "within" is that
-        * "in" only includes integers in the specified range,
-        * while "within" includes all values.
-        */
-       function within() {
-               var range_list, result;
-
-               // within_relation = expr ('not')? 'within' range_list
-               result = sequence(
-                       [expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList]
-               );
-
-               if (result !== null) {
-                       debug(' -- passed within');
-
-                       range_list = result[5];
-
-                       if ((result[0] >= parseInt(range_list[0], 10)) &&
-                               (result[0] < parseInt(range_list[range_list.length - 1], 10))) {
-
-                               return (result[1][0] !== 'not');
-                       }
-
-                       return (result[1][0] === 'not');
-               }
-
-               debug(' -- failed within ');
-
-               return null;
-       }
-
-       // relation      = is_relation | in_relation | within_relation
-       relation = choice([is, not_in, isnot, _in, within]);
-
-       // and_condition = relation ('and' relation)*
-       function and() {
-               var i,
-                       result = sequence([relation, nOrMore(0, andTail)]);
-
-               if (result) {
-                       if (!result[0]) {
-                               return false;
-                       }
-
-                       for (i = 0; i < result[1].length; i++) {
-                               if (!result[1][i]) {
-                                       return false;
-                               }
-                       }
-
-                       return true;
-               }
-
-               debug(' -- failed and');
-
-               return null;
-       }
-
-       // ('and' relation)*
-       function andTail() {
-               var result = sequence([whitespace, _and_, whitespace, relation]);
-
-               if (result !== null) {
-                       debug(' -- passed andTail' + result);
-
-                       return result[3];
-               }
-
-               debug(' -- failed andTail');
-
-               return null;
-
-       }
-       //  ('or' and_condition)*
-       function orTail() {
-               var result = sequence([whitespace, _or_, whitespace, and]);
-
-               if (result !== null) {
-                       debug(' -- passed orTail: ' + result[3]);
-
-                       return result[3];
-               }
-
-               debug(' -- failed orTail');
-
-               return null;
-       }
-
-       // condition     = and_condition ('or' and_condition)*
-       function condition() {
-               var i,
-                       result = sequence([and, nOrMore(0, orTail)]);
-
-               if (result) {
-                       for (i = 0; i < result[1].length; i++) {
-                               if (result[1][i]) {
-                                       return true;
-                               }
-                       }
-
-                       return result[0];
-               }
-
-               return false;
-       }
-
-       result = condition();
-
-       /**
-        * For success, the pos must have gotten to the end of the rule
-        * and returned a non-null.
-        * n.b. This is part of language infrastructure,
-        * so we do not throw an internationalizable message.
-        */
-       if (result === null) {
-               throw new Error('Parse error at position ' + pos.toString() + ' for rule: ' + rule);
-       }
-
-       if (pos !== rule.length) {
-               debug('Warning: Rule not parsed completely. Parser stopped at ' + rule.substr(0, pos) + ' for rule: ' + rule);
-       }
-
-       return result;
-}
-
-/* pluralRuleParser ends here */
-mw.libs.pluralRuleParser = pluralRuleParser;
-module.exports = pluralRuleParser;
-
-} )( mediaWiki );
diff --git a/resources/src/mediawiki.libs/mediawiki.libs.jpegmeta.js b/resources/src/mediawiki.libs/mediawiki.libs.jpegmeta.js
deleted file mode 100644 (file)
index d837420..0000000
+++ /dev/null
@@ -1,742 +0,0 @@
-/**
- * This is JsJpegMeta v1.0
- * From: https://code.google.com/p/jsjpegmeta/downloads/list
- * From: https://github.com/bennoleslie/jsjpegmeta/blob/v1.0.0/jpegmeta.js
- *
- * Ported to MediaWiki ResourceLoader by Bryan Tong Minh
- * Changes:
- * - Add closure.
- * - Add this.JpegMeta assignment to expose it as global.
- * - Add export as module.
- * - Add mw.libs.jpegmeta wrapper.
- */
-
-( function ( mw ) {
-       /*
-       Copyright (c) 2009 Ben Leslie
-       
-       Permission is hereby granted, free of charge, to any person obtaining a copy
-       of this software and associated documentation files (the "Software"), to deal
-       in the Software without restriction, including without limitation the rights
-       to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-       copies of the Software, and to permit persons to whom the Software is
-       furnished to do so, subject to the following conditions:
-       
-       The above copyright notice and this permission notice shall be included in
-       all copies or substantial portions of the Software.
-       
-       THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-       IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-       FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-       AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-       LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-       OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-       THE SOFTWARE.
-       */
-       
-       /*
-        This JavaScript library is used to parse meta-data from files 
-        with mime-type image/jpeg.
-       
-        Include it with something like:
-       
-          <script type="text/javascript" src="jpegmeta.js"></script>
-       
-        This adds a single 'module' object called 'JpegMeta' to the global
-        namespace.
-       
-        Public Functions
-        ----------------
-        JpegMeta.parseNum - parse unsigned integers from binary data
-        JpegMeta.parseSnum - parse signed integers from binary data
-       
-        Public Classes
-        --------------
-        JpegMeta.Rational - A rational number class
-        JpegMeta.JfifSegment
-        JpegMeta.ExifSegment
-        JpegMeta.JpegFile - Primary class for Javascript parsing
-       */
-
-       var JpegMeta = {};
-       // MediaWiki: Expose as global
-       this.JpegMeta = JpegMeta;
-       
-       /* 
-          parse an unsigned number of size bytes at offset in some binary string data.
-          If endian
-          is "<" parse the data as little endian, if endian
-          is ">" parse as big-endian.
-       */
-       JpegMeta.parseNum = function parseNum(endian, data, offset, size) {
-           var i;
-           var ret;
-           var big_endian = (endian === ">");
-           if (offset === undefined) offset = 0;
-           if (size === undefined) size = data.length - offset;
-           for (big_endian ? i = offset : i = offset + size - 1; 
-                big_endian ? i < offset + size : i >= offset; 
-                big_endian ? i++ : i--) {
-               ret <<= 8;
-               ret += data.charCodeAt(i);
-           }
-           return ret;
-       };
-       
-       /* 
-          parse an signed number of size bytes at offset in some binary string data.
-          If endian
-          is "<" parse the data as little endian, if endian
-          is ">" parse as big-endian.
-       */
-       JpegMeta.parseSnum = function parseSnum(endian, data, offset, size) {
-           var i;
-           var ret;
-           var neg;
-           var big_endian = (endian === ">");
-           if (offset === undefined) offset = 0;
-           if (size === undefined) size = data.length - offset;
-           for (big_endian ? i = offset : i = offset + size - 1; 
-                big_endian ? i < offset + size : i >= offset; 
-                big_endian ? i++ : i--) {
-               if (neg === undefined) {
-                   /* Negative if top bit is set */
-                   neg = (data.charCodeAt(i) & 0x80) === 0x80;
-               }
-               ret <<= 8;
-               /* If it is negative we invert the bits */
-               ret += neg ? ~data.charCodeAt(i) & 0xff: data.charCodeAt(i);
-           }
-           if (neg) {
-               /* If it is negative we do two's complement */
-               ret += 1;
-               ret *= -1;
-           }
-           return ret;
-       };
-       
-       /* Rational number class */
-       JpegMeta.Rational = function Rational(num, den)
-       {
-           this.num = num;
-           this.den = den || 1;
-           return this;
-       };
-       
-       /* Rational number methods */
-       JpegMeta.Rational.prototype.toString = function toString() {
-           if (this.num === 0) {
-               return "" + this.num;
-           }
-           if (this.den === 1) {
-               return "" + this.num;
-           }
-           if (this.num === 1) {
-               return this.num + " / " + this.den;
-           }
-           return this.num / this.den; // + "/" + this.den;
-       };
-       
-       JpegMeta.Rational.prototype.asFloat = function asFloat() {
-           return this.num / this.den;
-       };
-       
-       /* MetaGroup class */
-       JpegMeta.MetaGroup = function MetaGroup(fieldName, description) {
-           this.fieldName = fieldName;
-           this.description = description;
-           this.metaProps = {};
-           return this;
-       };
-       
-       JpegMeta.MetaGroup.prototype._addProperty = function _addProperty(fieldName, description, value) {
-           var property = new JpegMeta.MetaProp(fieldName, description, value);
-           this[property.fieldName] = property;
-           this.metaProps[property.fieldName] = property;
-       };
-       
-       JpegMeta.MetaGroup.prototype.toString = function toString() {
-           return "[MetaGroup " + this.description + "]";
-       };
-
-       /* MetaProp class */
-       JpegMeta.MetaProp = function MetaProp(fieldName, description, value) {
-           this.fieldName = fieldName;
-           this.description = description;
-           this.value = value;
-           return this;
-       };
-       
-       JpegMeta.MetaProp.prototype.toString = function toString() {
-           return "" + this.value;
-       };
-
-       /* JpegFile class */
-       JpegMeta.JpegFile = function JpegFile(binary_data, filename) {
-           /* Change this to EOI if we want to parse. */
-           var break_segment = this._SOS;
-           
-           this.metaGroups = {};
-           this._binary_data = binary_data;
-           this.filename = filename;
-           
-           /* Go through and parse. */
-           var pos = 0;
-           var pos_start_of_segment = 0;
-           var delim;
-           var mark;
-           var _mark;
-           var segsize;
-           var headersize;
-           var mark_code;
-           var mark_fn;
-       
-           /* Check to see if this looks like a JPEG file */
-           if (this._binary_data.slice(0, 2) !== this._SOI_MARKER) {
-               throw new Error("Doesn't look like a JPEG file. First two bytes are " + 
-                               this._binary_data.charCodeAt(0) + "," + 
-                               this._binary_data.charCodeAt(1) + ".");
-           }
-           
-           pos += 2;
-           
-           while (pos < this._binary_data.length) {
-               delim = this._binary_data.charCodeAt(pos++);
-               mark = this._binary_data.charCodeAt(pos++);
-               
-               pos_start_of_segment = pos;
-               
-               if (delim != this._DELIM) {
-                   break;
-               }
-               
-               if (mark === break_segment) {
-                   break;
-               }
-               
-               headersize = JpegMeta.parseNum(">", this._binary_data, pos, 2);
-               
-               /* Find the end */
-               pos += headersize;
-               while (pos < this._binary_data.length) {
-                   delim = this._binary_data.charCodeAt(pos++);
-                   if (delim == this._DELIM) {
-                       _mark = this._binary_data.charCodeAt(pos++);
-                       if (_mark != 0x0) {
-                           pos -= 2;
-                           break;
-                       }
-                   }
-               }
-               
-               segsize = pos - pos_start_of_segment;
-               
-               if (this._markers[mark]) {
-                   mark_code = this._markers[mark][0];
-                   mark_fn = this._markers[mark][1];
-               } else {
-                   mark_code = "UNKN";
-                   mark_fn = undefined;
-               }
-               
-               if (mark_fn) {
-                   this[mark_fn](mark, pos_start_of_segment + 2);
-               }
-               
-           }
-           
-           if (this.general === undefined) {
-               throw Error("Invalid JPEG file.");
-           }
-           
-           return this;
-       };
-       
-       this.JpegMeta.JpegFile.prototype.toString = function () {
-           return "[JpegFile " + this.filename + " " + 
-               this.general.type + " " + 
-               this.general.pixelWidth + "x" + 
-               this.general.pixelHeight +
-               " Depth: " + this.general.depth + "]";
-       };
-       
-       /* Some useful constants */
-       this.JpegMeta.JpegFile.prototype._SOI_MARKER = '\xff\xd8';
-       this.JpegMeta.JpegFile.prototype._DELIM = 0xff;
-       this.JpegMeta.JpegFile.prototype._EOI = 0xd9;
-       this.JpegMeta.JpegFile.prototype._SOS = 0xda;
-       
-       this.JpegMeta.JpegFile.prototype._sofHandler = function _sofHandler (mark, pos) {
-           if (this.general !== undefined) {
-               throw Error("Unexpected multiple-frame image");
-           }
-       
-           this._addMetaGroup("general", "General");
-           this.general._addProperty("depth", "Depth", JpegMeta.parseNum(">", this._binary_data, pos, 1));
-           this.general._addProperty("pixelHeight", "Pixel Height", JpegMeta.parseNum(">", this._binary_data, pos + 1, 2));
-           this.general._addProperty("pixelWidth", "Pixel Width",JpegMeta.parseNum(">", this._binary_data, pos + 3, 2));
-           this.general._addProperty("type", "Type", this._markers[mark][2]);
-       };
-       
-       /* JFIF idents */
-       this.JpegMeta.JpegFile.prototype._JFIF_IDENT = "JFIF\x00";
-       this.JpegMeta.JpegFile.prototype._JFXX_IDENT = "JFXX\x00";
-       
-       /* Exif idents */
-       this.JpegMeta.JpegFile.prototype._EXIF_IDENT = "Exif\x00";
-       
-       /* TIFF types */
-       this.JpegMeta.JpegFile.prototype._types = {
-           /* The format is identifier : ["type name", type_size_in_bytes ] */
-           1 : ["BYTE", 1],
-           2 : ["ASCII", 1],
-           3 : ["SHORT", 2],
-           4 : ["LONG", 4],
-           5 : ["RATIONAL", 8],
-           6 : ["SBYTE", 1],
-           7 : ["UNDEFINED", 1],
-           8 : ["SSHORT", 2],
-           9 : ["SLONG", 4],
-           10 : ["SRATIONAL", 8],
-           11 : ["FLOAT", 4],
-           12 : ["DOUBLE", 8]
-       };
-       
-       this.JpegMeta.JpegFile.prototype._tifftags = {
-           /* A. Tags relating to image data structure */
-           256 : ["Image width", "ImageWidth"],
-           257 : ["Image height", "ImageLength"],
-           258 : ["Number of bits per component", "BitsPerSample"],
-           259 : ["Compression scheme", "Compression", 
-                  {1 : "uncompressed", 6 : "JPEG compression" }],
-           262 : ["Pixel composition", "PhotmetricInerpretation",
-                  {2 : "RGB", 6 : "YCbCr"}],
-           274 : ["Orientation of image", "Orientation",
-                  /* FIXME: Check the mirror-image / reverse encoding and rotation */
-                  {1 : "Normal", 2 : "Reverse?", 
-                   3 : "Upside-down", 4 : "Upside-down Reverse",
-                   5 : "90 degree CW", 6 : "90 degree CW reverse",
-                   7 : "90 degree CCW", 8 : "90 degree CCW reverse"}],
-           277 : ["Number of components", "SamplesPerPixel"],
-           284 : ["Image data arrangement", "PlanarConfiguration",
-                  {1 : "chunky format", 2 : "planar format"}],
-           530 : ["Subsampling ratio of Y to C", "YCbCrSubSampling"],
-           531 : ["Y and C positioning", "YCbCrPositioning",
-                  {1 : "centered", 2 : "co-sited"}],
-           282 : ["X Resolution", "XResolution"],
-           283 : ["Y Resolution", "YResolution"],
-           296 : ["Resolution Unit", "ResolutionUnit",
-                  {2 : "inches", 3 : "centimeters"}],
-           /* B. Tags realting to recording offset */
-           273 : ["Image data location", "StripOffsets"],
-           278 : ["Number of rows per strip", "RowsPerStrip"],
-           279 : ["Bytes per compressed strip", "StripByteCounts"],
-           513 : ["Offset to JPEG SOI", "JPEGInterchangeFormat"],
-           514 : ["Bytes of JPEG Data", "JPEGInterchangeFormatLength"],
-           /* C. Tags relating to image data characteristics */
-           301 : ["Transfer function", "TransferFunction"],
-           318 : ["White point chromaticity", "WhitePoint"],
-           319 : ["Chromaticities of primaries", "PrimaryChromaticities"],
-           529 : ["Color space transformation matrix coefficients", "YCbCrCoefficients"],
-           532 : ["Pair of black and white reference values", "ReferenceBlackWhite"],
-           /* D. Other tags */
-           306 : ["Date and time", "DateTime"],
-           270 : ["Image title", "ImageDescription"],
-           271 : ["Make", "Make"],
-           272 : ["Model", "Model"],
-           305 : ["Software", "Software"],
-           315 : ["Person who created the image", "Artist"],
-           316 : ["Host Computer", "HostComputer"],
-           33432 : ["Copyright holder", "Copyright"],
-           
-           34665 : ["Exif tag", "ExifIfdPointer"],
-           34853 : ["GPS tag", "GPSInfoIfdPointer"]
-       };
-       
-       this.JpegMeta.JpegFile.prototype._exiftags = {
-           /* Tag Support Levels (2) - 0th IFX Exif Private Tags */
-           /* A. Tags Relating to Version */
-           36864 : ["Exif Version", "ExifVersion"],
-           40960 : ["FlashPix Version", "FlashpixVersion"],
-           
-           /* B. Tag Relating to Image Data Characteristics */
-           40961 : ["Color Space", "ColorSpace"],
-           
-           /* C. Tags Relating to Image Configuration */
-           37121 : ["Meaning of each component", "ComponentsConfiguration"],
-           37122 : ["Compressed Bits Per Pixel", "CompressedBitsPerPixel"],
-           40962 : ["Pixel X Dimension", "PixelXDimension"],
-           40963 : ["Pixel Y Dimension", "PixelYDimension"],
-           
-           /* D. Tags Relating to User Information */
-           37500 : ["Manufacturer notes", "MakerNote"],
-           37510 : ["User comments", "UserComment"],
-           
-           /* E. Tag Relating to Related File Information */
-           40964 : ["Related audio file", "RelatedSoundFile"],
-           
-           /* F. Tags Relating to Date and Time */
-           36867 : ["Date Time Original", "DateTimeOriginal"],
-           36868 : ["Date Time Digitized", "DateTimeDigitized"],
-           37520 : ["DateTime subseconds", "SubSecTime"],
-           37521 : ["DateTimeOriginal subseconds", "SubSecTimeOriginal"],
-           37522 : ["DateTimeDigitized subseconds", "SubSecTimeDigitized"],
-           
-           /* G. Tags Relating to Picture-Taking Conditions */
-           33434 : ["Exposure time", "ExposureTime"],
-           33437 : ["FNumber", "FNumber"],
-           34850 : ["Exposure program", "ExposureProgram"],
-           34852 : ["Spectral sensitivity", "SpectralSensitivity"],
-           34855 : ["ISO Speed Ratings", "ISOSpeedRatings"],
-           34856 : ["Optoelectric coefficient", "OECF"],
-           37377 : ["Shutter Speed",  "ShutterSpeedValue"],
-           37378 : ["Aperture Value", "ApertureValue"],
-           37379 : ["Brightness", "BrightnessValue"],
-           37380 : ["Exposure Bias Value", "ExposureBiasValue"],
-           37381 : ["Max Aperture Value", "MaxApertureValue"],
-           37382 : ["Subject Distance", "SubjectDistance"],
-           37383 : ["Metering Mode", "MeteringMode"],
-           37384 : ["Light Source", "LightSource"],
-           37385 : ["Flash", "Flash"],
-           37386 : ["Focal Length", "FocalLength"],
-           37396 : ["Subject Area", "SubjectArea"],
-           41483 : ["Flash Energy", "FlashEnergy"],
-           41484 : ["Spatial Frequency Response", "SpatialFrequencyResponse"],
-           41486 : ["Focal Plane X Resolution", "FocalPlaneXResolution"],
-           41487 : ["Focal Plane Y Resolution", "FocalPlaneYResolution"],
-           41488 : ["Focal Plane Resolution Unit", "FocalPlaneResolutionUnit"],
-           41492 : ["Subject Location", "SubjectLocation"],
-           41493 : ["Exposure Index", "ExposureIndex"],
-           41495 : ["Sensing Method", "SensingMethod"],
-           41728 : ["File Source", "FileSource"],
-           41729 : ["Scene Type", "SceneType"],
-           41730 : ["CFA Pattern", "CFAPattern"],
-           41985 : ["Custom Rendered", "CustomRendered"],
-           41986 : ["Exposure Mode", "Exposure Mode"],
-           41987 : ["White Balance", "WhiteBalance"],
-           41988 : ["Digital Zoom Ratio", "DigitalZoomRatio"],
-           41990 : ["Scene Capture Type", "SceneCaptureType"],
-           41991 : ["Gain Control", "GainControl"],
-           41992 : ["Contrast", "Contrast"],
-           41993 : ["Saturation", "Saturation"],
-           41994 : ["Sharpness", "Sharpness"],
-           41995 : ["Device settings description", "DeviceSettingDescription"],
-           41996 : ["Subject distance range", "SubjectDistanceRange"],
-           
-           /* H. Other Tags */
-           42016 : ["Unique image ID", "ImageUniqueID"],
-           
-           40965 : ["Interoperability tag", "InteroperabilityIFDPointer"]
-       };
-       
-       this.JpegMeta.JpegFile.prototype._gpstags = {
-           /* A. Tags Relating to GPS */
-           0 : ["GPS tag version", "GPSVersionID"],
-           1 : ["North or South Latitude", "GPSLatitudeRef"],
-           2 : ["Latitude", "GPSLatitude"],
-           3 : ["East or West Longitude", "GPSLongitudeRef"],
-           4 : ["Longitude", "GPSLongitude"],
-           5 : ["Altitude reference", "GPSAltitudeRef"],
-           6 : ["Altitude", "GPSAltitude"],
-           7 : ["GPS time (atomic clock)", "GPSTimeStamp"],
-           8 : ["GPS satellites usedd for measurement", "GPSSatellites"],
-           9 : ["GPS receiver status", "GPSStatus"],
-           10 : ["GPS mesaurement mode", "GPSMeasureMode"],
-           11 : ["Measurement precision", "GPSDOP"],
-           12 : ["Speed unit", "GPSSpeedRef"],
-           13 : ["Speed of GPS receiver", "GPSSpeed"],
-           14 : ["Reference for direction of movement", "GPSTrackRef"],
-           15 : ["Direction of movement", "GPSTrack"],
-           16 : ["Reference for direction of image", "GPSImgDirectionRef"],
-           17 : ["Direction of image", "GPSImgDirection"],
-           18 : ["Geodetic survey data used", "GPSMapDatum"],
-           19 : ["Reference for latitude of destination", "GPSDestLatitudeRef"],
-           20 : ["Latitude of destination", "GPSDestLatitude"],
-           21 : ["Reference for longitude of destination", "GPSDestLongitudeRef"],
-           22 : ["Longitude of destination", "GPSDestLongitude"],
-           23 : ["Reference for bearing of destination", "GPSDestBearingRef"],
-           24 : ["Bearing of destination", "GPSDestBearing"],
-           25 : ["Reference for distance to destination", "GPSDestDistanceRef"],
-           26 : ["Distance to destination", "GPSDestDistance"],
-           27 : ["Name of GPS processing method", "GPSProcessingMethod"],
-           28 : ["Name of GPS area", "GPSAreaInformation"],
-           29 : ["GPS Date", "GPSDateStamp"],
-           30 : ["GPS differential correction", "GPSDifferential"]
-       };
-
-       this.JpegMeta.JpegFile.prototype._markers = {
-           /* Start Of Frame markers, non-differential, Huffman coding */
-           0xc0: ["SOF0", "_sofHandler", "Baseline DCT"],
-           0xc1: ["SOF1", "_sofHandler", "Extended sequential DCT"],
-           0xc2: ["SOF2", "_sofHandler", "Progressive DCT"],
-           0xc3: ["SOF3", "_sofHandler", "Lossless (sequential)"],
-           
-           /* Start Of Frame markers, differential, Huffman coding */
-           0xc5: ["SOF5", "_sofHandler", "Differential sequential DCT"],
-           0xc6: ["SOF6", "_sofHandler", "Differential progressive DCT"],
-           0xc7: ["SOF7", "_sofHandler", "Differential lossless (sequential)"],
-           
-           /* Start Of Frame markers, non-differential, arithmetic coding */
-           0xc8: ["JPG", null, "Reserved for JPEG extensions"],
-           0xc9: ["SOF9", "_sofHandler", "Extended sequential DCT"],
-           0xca: ["SOF10", "_sofHandler", "Progressive DCT"],
-           0xcb: ["SOF11", "_sofHandler", "Lossless (sequential)"],
-           
-           /* Start Of Frame markers, differential, arithmetic coding */
-           0xcd: ["SOF13", "_sofHandler", "Differential sequential DCT"],
-           0xce: ["SOF14", "_sofHandler", "Differential progressive DCT"],
-           0xcf: ["SOF15", "_sofHandler", "Differential lossless (sequential)"],
-           
-           /* Huffman table specification */
-           0xc4: ["DHT", null, "Define Huffman table(s)"],
-           0xcc: ["DAC", null, "Define arithmetic coding conditioning(s)"],
-           
-           /* Restart interval termination" */
-           0xd0: ["RST0", null, "Restart with modulo 8 count “0”"],
-           0xd1: ["RST1", null, "Restart with modulo 8 count “1”"],
-           0xd2: ["RST2", null, "Restart with modulo 8 count “2”"],
-           0xd3: ["RST3", null, "Restart with modulo 8 count “3”"],
-           0xd4: ["RST4", null, "Restart with modulo 8 count “4”"],
-           0xd5: ["RST5", null, "Restart with modulo 8 count “5”"],
-           0xd6: ["RST6", null, "Restart with modulo 8 count “6”"],
-           0xd7: ["RST7", null, "Restart with modulo 8 count “7”"],
-           
-           /* Other markers */
-           0xd8: ["SOI", null, "Start of image"],
-           0xd9: ["EOI", null, "End of image"],
-           0xda: ["SOS", null, "Start of scan"],
-           0xdb: ["DQT", null, "Define quantization table(s)"],
-           0xdc: ["DNL", null, "Define number of lines"],
-           0xdd: ["DRI", null, "Define restart interval"],
-           0xde: ["DHP", null, "Define hierarchical progression"],
-           0xdf: ["EXP", null, "Expand reference component(s)"],
-           0xe0: ["APP0", "_app0Handler", "Reserved for application segments"],
-           0xe1: ["APP1", "_app1Handler"],
-           0xe2: ["APP2", null],
-           0xe3: ["APP3", null],
-           0xe4: ["APP4", null],
-           0xe5: ["APP5", null],
-           0xe6: ["APP6", null],
-           0xe7: ["APP7", null],
-           0xe8: ["APP8", null],
-           0xe9: ["APP9", null],
-           0xea: ["APP10", null],
-           0xeb: ["APP11", null],
-           0xec: ["APP12", null],
-           0xed: ["APP13", null],
-           0xee: ["APP14", null],
-           0xef: ["APP15", null],
-           0xf0: ["JPG0", null], /* Reserved for JPEG extensions */
-           0xf1: ["JPG1", null],
-           0xf2: ["JPG2", null],
-           0xf3: ["JPG3", null],
-           0xf4: ["JPG4", null],
-           0xf5: ["JPG5", null],
-           0xf6: ["JPG6", null],
-           0xf7: ["JPG7", null],
-           0xf8: ["JPG8", null],
-           0xf9: ["JPG9", null],
-           0xfa: ["JPG10", null],
-           0xfb: ["JPG11", null],
-           0xfc: ["JPG12", null],
-           0xfd: ["JPG13", null],
-           0xfe: ["COM", null], /* Comment */
-           
-           /* Reserved markers */
-           0x01: ["JPG13", null] /* For temporary private use in arithmetic coding */
-           /* 02 -> bf are reserverd */
-       };
-
-       /* Private methods */
-       this.JpegMeta.JpegFile.prototype._addMetaGroup = function _addMetaGroup(name, description) {
-           var group = new JpegMeta.MetaGroup(name, description);
-           this[group.fieldName] = group;
-           this.metaGroups[group.fieldName] = group;
-           return group;
-       };
-
-       this.JpegMeta.JpegFile.prototype._parseIfd = function _parseIfd(endian, _binary_data, base, ifd_offset, tags, name, description) {
-           var num_fields = JpegMeta.parseNum(endian, _binary_data, base + ifd_offset, 2);
-           /* Per tag variables */
-           var i, j;
-           var tag_base;
-           var tag_field;
-           var type, type_field, type_size;
-           var num_values;
-           var value_offset;
-           var value;
-           var _val;
-           var num;
-           var den;
-           
-           var group;
-           
-           group = this._addMetaGroup(name, description);
-       
-           for (var i = 0; i < num_fields; i++) {
-               /* parse the field */
-               tag_base = base + ifd_offset + 2 + (i * 12);
-               tag_field = JpegMeta.parseNum(endian, _binary_data, tag_base, 2);
-               type_field = JpegMeta.parseNum(endian, _binary_data, tag_base + 2, 2);
-               num_values = JpegMeta.parseNum(endian, _binary_data, tag_base + 4, 4);
-               value_offset = JpegMeta.parseNum(endian, _binary_data, tag_base + 8, 4);
-               if (this._types[type_field] === undefined) {
-                   continue;
-               }
-               type = this._types[type_field][0];
-               type_size = this._types[type_field][1];
-               
-               if (type_size * num_values <= 4) {
-                   /* Data is in-line */
-                   value_offset = tag_base + 8;
-               } else {
-                   value_offset = base + value_offset;
-               }
-               
-               /* Read the value */
-               if (type == "UNDEFINED") {
-                   value = _binary_data.slice(value_offset, value_offset + num_values);
-               } else if (type == "ASCII") {
-                   value = _binary_data.slice(value_offset, value_offset + num_values);
-                   value = value.split('\x00')[0];
-                   /* strip trail nul */
-               } else {
-                   value = new Array();
-                   for (j = 0; j < num_values; j++, value_offset += type_size) {
-                       if (type == "BYTE" || type == "SHORT" || type == "LONG") {
-                           value.push(JpegMeta.parseNum(endian, _binary_data, value_offset, type_size));
-                       }
-                       if (type == "SBYTE" || type == "SSHORT" || type == "SLONG") {
-                           value.push(JpegMeta.parseSnum(endian, _binary_data, value_offset, type_size));
-                       }
-                       if (type == "RATIONAL") {
-                           num = JpegMeta.parseNum(endian, _binary_data, value_offset, 4);
-                           den = JpegMeta.parseNum(endian, _binary_data, value_offset + 4, 4);
-                           value.push(new JpegMeta.Rational(num, den));
-                       }
-                       if (type == "SRATIONAL") {
-                           num = JpegMeta.parseSnum(endian, _binary_data, value_offset, 4);
-                           den = JpegMeta.parseSnum(endian, _binary_data, value_offset + 4, 4);
-                           value.push(new JpegMeta.Rational(num, den));
-                       }
-                       value.push();
-                   }
-                   if (num_values === 1) {
-                       value = value[0];
-                   }
-               }
-               if (tags[tag_field] !== undefined) {
-                       group._addProperty(tags[tag_field][1], tags[tag_field][0], value);
-               }
-           }
-       };
-
-       this.JpegMeta.JpegFile.prototype._jfifHandler = function _jfifHandler(mark, pos) {
-           if (this.jfif !== undefined) {
-               throw Error("Multiple JFIF segments found");
-           }
-           this._addMetaGroup("jfif", "JFIF");
-           this.jfif._addProperty("version_major", "Version Major", this._binary_data.charCodeAt(pos + 5));
-           this.jfif._addProperty("version_minor", "Version Minor", this._binary_data.charCodeAt(pos + 6));
-           this.jfif._addProperty("version", "JFIF Version", this.jfif.version_major.value + "." + this.jfif.version_minor.value);
-           this.jfif._addProperty("units", "Density Unit", this._binary_data.charCodeAt(pos + 7));
-           this.jfif._addProperty("Xdensity", "X density", JpegMeta.parseNum(">", this._binary_data, pos + 8, 2));
-           this.jfif._addProperty("Ydensity", "Y Density", JpegMeta.parseNum(">", this._binary_data, pos + 10, 2));
-           this.jfif._addProperty("Xthumbnail", "X Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 12, 1));
-           this.jfif._addProperty("Ythumbnail", "Y Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 13, 1));
-       };
-
-       /* Handle app0 segments */
-       this.JpegMeta.JpegFile.prototype._app0Handler = function app0Handler(mark, pos) {
-           var ident = this._binary_data.slice(pos, pos + 5);
-           if (ident == this._JFIF_IDENT) {
-               this._jfifHandler(mark, pos);
-           } else if (ident == this._JFXX_IDENT) {
-               /* Don't handle JFXX Ident yet */
-           } else {
-               /* Don't know about other idents */
-           }
-       };
-
-       /* Handle app1 segments */
-       this.JpegMeta.JpegFile.prototype._app1Handler = function _app1Handler(mark, pos) {
-           var ident = this._binary_data.slice(pos, pos + 5);
-           if (ident == this._EXIF_IDENT) {
-               this._exifHandler(mark, pos + 6);
-           } else {
-               /* Don't know about other idents */
-           }
-       };
-
-       /* Handle exif segments */
-       JpegMeta.JpegFile.prototype._exifHandler = function _exifHandler(mark, pos) {
-           if (this.exif !== undefined) {
-               throw new Error("Multiple JFIF segments found");
-           }
-           
-           /* Parse this TIFF header */
-           var endian;
-           var magic_field;
-           var ifd_offset;
-           var primary_ifd, exif_ifd, gps_ifd;
-           var endian_field = this._binary_data.slice(pos, pos + 2);
-           
-           /* Trivia: This 'I' is for Intel, the 'M' is for Motorola */
-           if (endian_field === "II") {
-               endian = "<";
-           } else if (endian_field === "MM") {
-               endian = ">";
-           } else {
-               throw new Error("Malformed TIFF meta-data. Unknown endianess: " + endian_field);
-           }
-           
-           magic_field = JpegMeta.parseNum(endian, this._binary_data, pos + 2, 2);
-           
-           if (magic_field !== 42) {
-               throw new Error("Malformed TIFF meta-data. Bad magic: " + magic_field);
-           }
-           
-           ifd_offset = JpegMeta.parseNum(endian, this._binary_data, pos + 4, 4);
-           
-           /* Parse 0th IFD */
-           this._parseIfd(endian, this._binary_data, pos, ifd_offset, this._tifftags, "tiff", "TIFF");
-           
-           if (this.tiff.ExifIfdPointer) {
-               this._parseIfd(endian, this._binary_data, pos, this.tiff.ExifIfdPointer.value, this._exiftags, "exif", "Exif");
-           }
-           
-           if (this.tiff.GPSInfoIfdPointer) {
-               this._parseIfd(endian, this._binary_data, pos, this.tiff.GPSInfoIfdPointer.value, this._gpstags, "gps", "GPS");
-               if (this.gps.GPSLatitude) {
-                   var latitude;
-                   latitude = this.gps.GPSLatitude.value[0].asFloat() + 
-                       (1 / 60) * this.gps.GPSLatitude.value[1].asFloat() + 
-                       (1 / 3600) * this.gps.GPSLatitude.value[2].asFloat();
-                   if (this.gps.GPSLatitudeRef.value === "S") {
-                       latitude = -latitude;
-                   }
-                   this.gps._addProperty("latitude", "Dec. Latitude", latitude);
-               }
-               if (this.gps.GPSLongitude) {
-                   var longitude;
-                   longitude = this.gps.GPSLongitude.value[0].asFloat() + 
-                       (1 / 60) * this.gps.GPSLongitude.value[1].asFloat() + 
-                       (1 / 3600) * this.gps.GPSLongitude.value[2].asFloat();
-                   if (this.gps.GPSLongitudeRef.value === "W") {
-                       longitude = -longitude;
-                   }
-                   this.gps._addProperty("longitude", "Dec. Longitude", longitude);
-               }
-           }
-       };
-
-       // MediaWiki: Export as module
-       module.exports = function( fileReaderResult, fileName ) {
-               return new JpegMeta.JpegFile( fileReaderResult, fileName );
-       };
-
-       // MediaWiki: Add mw.libs wrapper
-       // @deprecated since 1.31
-       mw.log.deprecate( mw.libs, 'jpegmeta', module.exports );
-
-}( mediaWiki ) );
index 1476241..244154b 100644 (file)
@@ -4,9 +4,12 @@
  */
 ( function ( mw, $ ) {
        $( function () {
-               var allowCloseWindow;
+               var allowCloseWindow, saveButton, restoreButton,
+                       oouiEnabled = $( '#mw-prefs-form' ).hasClass( 'mw-htmlform-ooui' );
 
-               // Check if all of the form values are unchanged
+               // Check if all of the form values are unchanged.
+               // (This function could be changed to infuse and check OOUI widgets, but that would only make it
+               // slower and more complicated. It works fine to treat them as HTML elements.)
                function isPrefsChanged() {
                        var inputs = $( '#mw-prefs-form :input[name]' ),
                                input, $input, inputType,
                        return false;
                }
 
-               // Disable the button to save preferences unless preferences have changed
-               // Check if preferences have been changed before JS has finished loading
-               $( '#prefcontrol' ).prop( 'disabled', !isPrefsChanged() );
-               $( '#preferences > fieldset' ).on( 'change keyup mouseup', function () {
+               if ( oouiEnabled ) {
+                       saveButton = OO.ui.infuse( $( '#prefcontrol' ) );
+                       restoreButton = OO.ui.infuse( $( '#mw-prefs-restoreprefs' ) );
+
+                       // Disable the button to save preferences unless preferences have changed
+                       // Check if preferences have been changed before JS has finished loading
+                       saveButton.setDisabled( !isPrefsChanged() );
+                       $( '#preferences .oo-ui-fieldsetLayout' ).on( 'change keyup mouseup', function () {
+                               saveButton.setDisabled( !isPrefsChanged() );
+                       } );
+               } else {
+                       // Disable the button to save preferences unless preferences have changed
+                       // Check if preferences have been changed before JS has finished loading
                        $( '#prefcontrol' ).prop( 'disabled', !isPrefsChanged() );
-               } );
+                       $( '#preferences > fieldset' ).on( 'change keyup mouseup', function () {
+                               $( '#prefcontrol' ).prop( 'disabled', !isPrefsChanged() );
+                       } );
+               }
 
                // Set up a message to notify users if they try to leave the page without
                // saving.
                        message: mw.msg( 'prefswarning-warning', mw.msg( 'saveprefs' ) ),
                        namespace: 'prefswarning'
                } );
-               $( '#mw-prefs-form' ).submit( $.proxy( allowCloseWindow, 'release' ) );
-               $( '#mw-prefs-restoreprefs' ).click( $.proxy( allowCloseWindow, 'release' ) );
+               $( '#mw-prefs-form' ).on( 'submit', $.proxy( allowCloseWindow, 'release' ) );
+               if ( oouiEnabled ) {
+                       restoreButton.on( 'click', function () {
+                               allowCloseWindow.release();
+                               // The default behavior of events in OOUI is always prevented. Follow the link manually.
+                               // Note that middle-click etc. still works, as it doesn't emit a OOUI 'click' event.
+                               location.href = restoreButton.getHref();
+                       } );
+               } else {
+                       $( '#mw-prefs-restoreprefs' ).on( 'click', $.proxy( allowCloseWindow, 'release' ) );
+               }
        } );
 }( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki.special/mediawiki.special.preferences.editfont.js b/resources/src/mediawiki.special/mediawiki.special.preferences.editfont.js
new file mode 100644 (file)
index 0000000..fe48886
--- /dev/null
@@ -0,0 +1,32 @@
+/*!
+ * JavaScript for Special:Preferences: editfont field enhancements.
+ */
+( function ( mw, $ ) {
+       $( function () {
+               var widget, lastValue;
+
+               try {
+                       widget = OO.ui.infuse( $( '#mw-input-wpeditfont' ) );
+               } catch ( err ) {
+                       // This preference could theoretically be disabled ($wgHiddenPrefs)
+                       return;
+               }
+
+               // Style options
+               widget.dropdownWidget.menu.items.forEach( function ( item ) {
+                       item.$label.addClass( 'mw-editfont-' + item.getData() );
+               } );
+
+               function updateLabel( value ) {
+                       // Style selected item label
+                       widget.dropdownWidget.$label
+                               .removeClass( 'mw-editfont-' + lastValue )
+                               .addClass( 'mw-editfont-' + value );
+                       lastValue = value;
+               }
+
+               widget.on( 'change', updateLabel );
+               updateLabel( widget.getValue() );
+
+       } );
+}( mediaWiki, jQuery ) );
index 33b630a..2310377 100644 (file)
@@ -1,27 +1,29 @@
 /* Reuses colors from mediawiki.legacy/shared.css */
-.mw-email-not-authenticated .mw-input,
-.mw-email-none .mw-input {
+.mw-email-not-authenticated .oo-ui-labelWidget,
+.mw-email-none .oo-ui-labelWidget {
        border: 1px solid #fde29b;
        background-color: #fdf1d1;
        color: #000;
+       padding: 0.5em;
 }
 /* Authenticated email field has its own class too. Unstyled by default */
 /*
-.mw-email-authenticated .mw-input { }
+.mw-email-authenticated .oo-ui-labelWidget { }
 */
-/* This breaks due to nolabel styling */
-#preferences > fieldset td.mw-label {
-       width: 20%;
-}
 
-#preferences > fieldset table {
-       width: 100%;
+/* This is needed because add extra buttons in a weird way */
+.mw-prefs-buttons .mw-htmlform-submit-buttons {
+       margin: 0;
+       display: inline;
 }
-#preferences > fieldset table.mw-htmlform-matrix {
-       width: auto;
+
+.mw-prefs-buttons {
+       margin-top: 1em;
 }
 
-/* The CSS below is also for JS enabled version, because we want to prevent FOUC */
+#prefcontrol {
+       margin-right: 0.5em;
+}
 
 /*
  * Hide, but keep accessible for screen-readers.
        zoom: 1;
 }
 
-.client-nojs #preftoc {
-       display: none;
+/* Override OOUI styles so that dropdowns near the bottom of the form don't get clipped,
+ * e.g.'Appearance' / 'Threshold for stub link formatting'. This is hacky and bad, it would be
+ * better solved by setting overlays for the widgets, but we can't do it from PHP... */
+#preferences .oo-ui-panelLayout {
+       position: static;
+       overflow: visible;
+       -webkit-transform: none;
+       transform: none;
+}
+
+#preferences .oo-ui-panelLayout-framed .oo-ui-panelLayout-framed {
+       border-color: #c8ccd1;
+       border-width: 1px 0 0;
+       border-radius: 0;
+       padding-left: 0;
+       padding-right: 0;
+       box-shadow: none;
+}
+
+/* Tweak the margins to reduce the shifting of form contents
+ * after JS code loads and rearranges the page */
+.client-js #preferences > .oo-ui-panelLayout {
+       margin: 1em 0;
+}
+
+.client-js #preferences .oo-ui-panelLayout-framed .oo-ui-panelLayout-framed {
+       margin-left: 0.25em;
+}
+
+.client-js #preferences .oo-ui-tabPanelLayout {
+       padding-top: 0.5em;
+       padding-bottom: 0.5em;
 }
 
-.client-js #preferences > fieldset {
-       display: none;
+.client-js #preferences .oo-ui-tabPanelLayout .oo-ui-panelLayout-framed {
+       margin-left: 0;
+       margin-bottom: 0;
+       border: 0;
+       padding-top: 0;
 }
 
-/* Only the 1st tab is shown by default in JS mode */
-.client-js #preferences #mw-prefsection-personal {
+.client-js #preferences > .oo-ui-panelLayout > .oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-header {
+       margin-bottom: 1em;
+}
+
+/* Make the "Basic information" section more compact */
+/* OOUI's `align: 'left'` for FieldLayouts sucks, so we do our own */
+#mw-htmlform-info > .oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header {
+       width: 20%;
+       display: inline-block;
+       vertical-align: middle;
+       padding: 0;
+}
+
+#mw-htmlform-info > .oo-ui-fieldLayout-align-top .oo-ui-fieldLayout-help {
+       margin-right: 0;
+}
+
+#mw-htmlform-info > .oo-ui-fieldLayout.oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field {
+       width: 80%;
+       display: inline-block;
+       vertical-align: middle;
+}
+
+/* Expand the dropdown and textfield of "Time zone" field to the */
+/* usual maximum width and display them on separate lines. */
+#wpTimeCorrection .oo-ui-dropdownInputWidget,
+#wpTimeCorrection .oo-ui-textInputWidget {
        display: block;
+       max-width: 50em;
+}
+
+#wpTimeCorrection .oo-ui-textInputWidget {
+       margin-top: 0.5em;
 }
diff --git a/resources/src/mediawiki.special/mediawiki.special.preferences.styles.legacy.css b/resources/src/mediawiki.special/mediawiki.special.preferences.styles.legacy.css
new file mode 100644 (file)
index 0000000..33b630a
--- /dev/null
@@ -0,0 +1,47 @@
+/* Reuses colors from mediawiki.legacy/shared.css */
+.mw-email-not-authenticated .mw-input,
+.mw-email-none .mw-input {
+       border: 1px solid #fde29b;
+       background-color: #fdf1d1;
+       color: #000;
+}
+/* Authenticated email field has its own class too. Unstyled by default */
+/*
+.mw-email-authenticated .mw-input { }
+*/
+/* This breaks due to nolabel styling */
+#preferences > fieldset td.mw-label {
+       width: 20%;
+}
+
+#preferences > fieldset table {
+       width: 100%;
+}
+#preferences > fieldset table.mw-htmlform-matrix {
+       width: auto;
+}
+
+/* The CSS below is also for JS enabled version, because we want to prevent FOUC */
+
+/*
+ * Hide, but keep accessible for screen-readers.
+ * Like .mw-jump, #jump-to-nav from shared.css
+ */
+.client-js .mw-navigation-hint {
+       overflow: hidden;
+       height: 0;
+       zoom: 1;
+}
+
+.client-nojs #preftoc {
+       display: none;
+}
+
+.client-js #preferences > fieldset {
+       display: none;
+}
+
+/* Only the 1st tab is shown by default in JS mode */
+.client-js #preferences #mw-prefsection-personal {
+       display: block;
+}
index 0d97d68..c948ff0 100644 (file)
@@ -3,29 +3,10 @@
  */
 ( function ( mw, $ ) {
        $( function () {
-               var $preftoc, $preferences, $fieldsets, labelFunc, previousTab;
+               var $preferences, tabs, wrapper, previousTab;
 
-               labelFunc = function () {
-                       return this.id.replace( /^mw-prefsection/g, 'preftab' );
-               };
-
-               $preftoc = $( '#preftoc' );
                $preferences = $( '#preferences' );
 
-               $fieldsets = $preferences.children( 'fieldset' )
-                       .attr( {
-                               role: 'tabpanel',
-                               'aria-labelledby': labelFunc
-                       } );
-               $fieldsets.not( '#mw-prefsection-personal' )
-                       .hide()
-                       .attr( 'aria-hidden', 'true' );
-
-               // T115692: The following is kept for backwards compatibility with older skins
-               $preferences.addClass( 'jsprefs' );
-               $fieldsets.addClass( 'prefsection' );
-               $fieldsets.children( 'legend' ).addClass( 'mainLegend' );
-
                // Make sure the accessibility tip is selectable so that screen reader users take notice,
                // but hide it per default to reduce interface clutter. Also make sure it becomes visible
                // when selected. Similar to jquery.mw-jump
                                } else {
                                        $( this ).css( 'height', 'auto' );
                                }
-                       } ).insertBefore( $preftoc );
+                       } ).prependTo( '#mw-content-text' );
 
-               /**
-                * It uses document.getElementById for security reasons (HTML injections in $()).
-                *
-                * @ignore
-                * @param {string} name the name of a tab without the prefix ("mw-prefsection-")
-                * @param {string} [mode] A hash will be set according to the current
-                *  open section. Set mode 'noHash' to surpress this.
-                */
-               function switchPrefTab( name, mode ) {
-                       var $tab, scrollTop;
+               tabs = new OO.ui.IndexLayout( {
+                       expanded: false,
+                       // Do not remove focus from the tabs menu after choosing a tab
+                       autoFocus: false
+               } );
+
+               mw.config.get( 'wgPreferencesTabs' ).forEach( function ( tabConfig ) {
+                       var panel, $panelContents;
+
+                       panel = new OO.ui.TabPanelLayout( tabConfig.name, {
+                               expanded: false,
+                               label: tabConfig.label
+                       } );
+                       $panelContents = $( '#mw-prefsection-' + tabConfig.name );
+
+                       // Hide the unnecessary PHP PanelLayouts
+                       // (Do not use .remove(), as that would remove event handlers for everything inside them)
+                       $panelContents.parent().detach();
+
+                       panel.$element.append( $panelContents );
+                       tabs.addTabPanels( [ panel ] );
+
+                       // Remove duplicate labels
+                       // (This must be after .addTabPanels(), otherwise the tab item doesn't exist yet)
+                       $panelContents.children( 'legend' ).remove();
+                       $panelContents.attr( 'aria-labelledby', panel.getTabItem().getElementId() );
+               } );
+
+               wrapper = new OO.ui.PanelLayout( {
+                       expanded: false,
+                       padded: false,
+                       framed: true
+               } );
+               wrapper.$element.append( tabs.$element );
+               $preferences.prepend( wrapper.$element );
+
+               function updateHash( panel ) {
+                       var scrollTop, active;
                        // Handle hash manually to prevent jumping,
                        // therefore save and restore scrollTop to prevent jumping.
                        scrollTop = $( window ).scrollTop();
-                       if ( mode !== 'noHash' ) {
-                               location.hash = '#mw-prefsection-' + name;
+                       // Changing the hash apparently causes keyboard focus to be lost?
+                       // Save and restore it. This makes no sense though.
+                       active = document.activeElement;
+                       location.hash = '#mw-prefsection-' + panel.getName();
+                       if ( active ) {
+                               active.focus();
                        }
                        $( window ).scrollTop( scrollTop );
-
-                       $preftoc.find( 'li' ).removeClass( 'selected' )
-                               .find( 'a' ).attr( {
-                                       tabIndex: -1,
-                                       'aria-selected': 'false'
-                               } );
-
-                       $tab = $( document.getElementById( 'preftab-' + name ) );
-                       if ( $tab.length ) {
-                               $tab.attr( {
-                                       tabIndex: 0,
-                                       'aria-selected': 'true'
-                               } ).focus()
-                                       .parent().addClass( 'selected' );
-
-                               $preferences.children( 'fieldset' ).hide().attr( 'aria-hidden', 'true' );
-                               $( document.getElementById( 'mw-prefsection-' + name ) ).show().attr( 'aria-hidden', 'false' );
-                       }
                }
 
-               // Enable keyboard users to use left and right keys to switch tabs
-               $preftoc.on( 'keydown', function ( event ) {
-                       var keyLeft = 37,
-                               keyRight = 39,
-                               $el;
-
-                       if ( event.keyCode === keyLeft ) {
-                               $el = $( '#preftoc li.selected' ).prev().find( 'a' );
-                       } else if ( event.keyCode === keyRight ) {
-                               $el = $( '#preftoc li.selected' ).next().find( 'a' );
-                       } else {
-                               return;
+               tabs.on( 'set', updateHash );
+
+               /**
+                * @ignore
+                * @param {string} name the name of a tab without the prefix ("mw-prefsection-")
+                * @param {string} [mode] A hash will be set according to the current
+                *  open section. Set mode 'noHash' to supress this.
+                */
+               function switchPrefTab( name, mode ) {
+                       if ( mode === 'noHash' ) {
+                               tabs.off( 'set', updateHash );
                        }
-                       if ( $el.length > 0 ) {
-                               switchPrefTab( $el.attr( 'href' ).replace( '#mw-prefsection-', '' ) );
+                       tabs.setTabPanel( name );
+                       if ( mode === 'noHash' ) {
+                               tabs.on( 'set', updateHash );
                        }
-               } );
+               }
 
                // Jump to correct section as indicated by the hash.
                // This function is called onload and onhashchange.
                }
 
                $( '#mw-prefs-form' ).on( 'submit', function () {
-                       var value = $( $preftoc ).find( 'li.selected a' ).attr( 'id' ).replace( 'preftab-', '' );
+                       var value = tabs.getCurrentTabPanelName();
                        mw.storage.session.set( 'mwpreferences-prevTab', value );
                } );
 
diff --git a/resources/src/mediawiki.special/mediawiki.special.preferences.tabs.legacy.js b/resources/src/mediawiki.special/mediawiki.special.preferences.tabs.legacy.js
new file mode 100644 (file)
index 0000000..0d97d68
--- /dev/null
@@ -0,0 +1,143 @@
+/*!
+ * JavaScript for Special:Preferences: Tab navigation.
+ */
+( function ( mw, $ ) {
+       $( function () {
+               var $preftoc, $preferences, $fieldsets, labelFunc, previousTab;
+
+               labelFunc = function () {
+                       return this.id.replace( /^mw-prefsection/g, 'preftab' );
+               };
+
+               $preftoc = $( '#preftoc' );
+               $preferences = $( '#preferences' );
+
+               $fieldsets = $preferences.children( 'fieldset' )
+                       .attr( {
+                               role: 'tabpanel',
+                               'aria-labelledby': labelFunc
+                       } );
+               $fieldsets.not( '#mw-prefsection-personal' )
+                       .hide()
+                       .attr( 'aria-hidden', 'true' );
+
+               // T115692: The following is kept for backwards compatibility with older skins
+               $preferences.addClass( 'jsprefs' );
+               $fieldsets.addClass( 'prefsection' );
+               $fieldsets.children( 'legend' ).addClass( 'mainLegend' );
+
+               // Make sure the accessibility tip is selectable so that screen reader users take notice,
+               // but hide it per default to reduce interface clutter. Also make sure it becomes visible
+               // when selected. Similar to jquery.mw-jump
+               $( '<div>' ).addClass( 'mw-navigation-hint' )
+                       .text( mw.msg( 'prefs-tabs-navigation-hint' ) )
+                       .attr( 'tabIndex', 0 )
+                       .on( 'focus blur', function ( e ) {
+                               if ( e.type === 'blur' || e.type === 'focusout' ) {
+                                       $( this ).css( 'height', '0' );
+                               } else {
+                                       $( this ).css( 'height', 'auto' );
+                               }
+                       } ).insertBefore( $preftoc );
+
+               /**
+                * It uses document.getElementById for security reasons (HTML injections in $()).
+                *
+                * @ignore
+                * @param {string} name the name of a tab without the prefix ("mw-prefsection-")
+                * @param {string} [mode] A hash will be set according to the current
+                *  open section. Set mode 'noHash' to surpress this.
+                */
+               function switchPrefTab( name, mode ) {
+                       var $tab, scrollTop;
+                       // Handle hash manually to prevent jumping,
+                       // therefore save and restore scrollTop to prevent jumping.
+                       scrollTop = $( window ).scrollTop();
+                       if ( mode !== 'noHash' ) {
+                               location.hash = '#mw-prefsection-' + name;
+                       }
+                       $( window ).scrollTop( scrollTop );
+
+                       $preftoc.find( 'li' ).removeClass( 'selected' )
+                               .find( 'a' ).attr( {
+                                       tabIndex: -1,
+                                       'aria-selected': 'false'
+                               } );
+
+                       $tab = $( document.getElementById( 'preftab-' + name ) );
+                       if ( $tab.length ) {
+                               $tab.attr( {
+                                       tabIndex: 0,
+                                       'aria-selected': 'true'
+                               } ).focus()
+                                       .parent().addClass( 'selected' );
+
+                               $preferences.children( 'fieldset' ).hide().attr( 'aria-hidden', 'true' );
+                               $( document.getElementById( 'mw-prefsection-' + name ) ).show().attr( 'aria-hidden', 'false' );
+                       }
+               }
+
+               // Enable keyboard users to use left and right keys to switch tabs
+               $preftoc.on( 'keydown', function ( event ) {
+                       var keyLeft = 37,
+                               keyRight = 39,
+                               $el;
+
+                       if ( event.keyCode === keyLeft ) {
+                               $el = $( '#preftoc li.selected' ).prev().find( 'a' );
+                       } else if ( event.keyCode === keyRight ) {
+                               $el = $( '#preftoc li.selected' ).next().find( 'a' );
+                       } else {
+                               return;
+                       }
+                       if ( $el.length > 0 ) {
+                               switchPrefTab( $el.attr( 'href' ).replace( '#mw-prefsection-', '' ) );
+                       }
+               } );
+
+               // Jump to correct section as indicated by the hash.
+               // This function is called onload and onhashchange.
+               function detectHash() {
+                       var hash = location.hash,
+                               matchedElement, parentSection;
+                       if ( hash.match( /^#mw-prefsection-[\w]+$/ ) ) {
+                               mw.storage.session.remove( 'mwpreferences-prevTab' );
+                               switchPrefTab( hash.replace( '#mw-prefsection-', '' ) );
+                       } else if ( hash.match( /^#mw-[\w-]+$/ ) ) {
+                               matchedElement = document.getElementById( hash.slice( 1 ) );
+                               parentSection = $( matchedElement ).parent().closest( '[id^="mw-prefsection-"]' );
+                               if ( parentSection.length ) {
+                                       mw.storage.session.remove( 'mwpreferences-prevTab' );
+                                       // Switch to proper tab and scroll to selected item.
+                                       switchPrefTab( parentSection.attr( 'id' ).replace( 'mw-prefsection-', '' ), 'noHash' );
+                                       matchedElement.scrollIntoView();
+                               }
+                       }
+               }
+
+               $( window ).on( 'hashchange', function () {
+                       var hash = location.hash;
+                       if ( hash.match( /^#mw-[\w-]+/ ) ) {
+                               detectHash();
+                       } else if ( hash === '' ) {
+                               switchPrefTab( 'personal', 'noHash' );
+                       }
+               } )
+                       // Run the function immediately to select the proper tab on startup.
+                       .trigger( 'hashchange' );
+
+               // Restore the active tab after saving the preferences
+               previousTab = mw.storage.session.get( 'mwpreferences-prevTab' );
+               if ( previousTab ) {
+                       switchPrefTab( previousTab, 'noHash' );
+                       // Deleting the key, the tab states should be reset until we press Save
+                       mw.storage.session.remove( 'mwpreferences-prevTab' );
+               }
+
+               $( '#mw-prefs-form' ).on( 'submit', function () {
+                       var value = $( $preftoc ).find( 'li.selected a' ).attr( 'id' ).replace( 'preftab-', '' );
+                       mw.storage.session.set( 'mwpreferences-prevTab', value );
+               } );
+
+       } );
+}( mediaWiki, jQuery ) );
index 03656ee..a6ffae9 100644 (file)
@@ -3,14 +3,25 @@
  */
 ( function ( mw, $ ) {
        $( function () {
-               var
-                       $tzSelect, $tzTextbox, $localtimeHolder, servertime;
+               var $tzSelect, $tzTextbox, timezoneWidget, $localtimeHolder, servertime,
+                       oouiEnabled = $( '#mw-prefs-form' ).hasClass( 'mw-htmlform-ooui' );
 
                // Timezone functions.
                // Guesses Timezone from browser and updates fields onchange.
 
-               $tzSelect = $( '#mw-input-wptimecorrection' );
-               $tzTextbox = $( '#mw-input-wptimecorrection-other' );
+               if ( oouiEnabled ) {
+                       // This is identical to OO.ui.infuse( ... ), but it makes the class name of the result known.
+                       try {
+                               timezoneWidget = mw.widgets.SelectWithInputWidget.static.infuse( $( '#wpTimeCorrection' ) );
+                       } catch ( err ) {
+                               // This preference could theoretically be disabled ($wgHiddenPrefs)
+                               timezoneWidget = null;
+                       }
+               } else {
+                       $tzSelect = $( '#mw-input-wptimecorrection' );
+                       $tzTextbox = $( '#mw-input-wptimecorrection-other' );
+               }
+
                $localtimeHolder = $( '#wpLocalTime' );
                servertime = parseInt( $( 'input[name="wpServerTime"]' ).val(), 10 );
 
 
                function updateTimezoneSelection() {
                        var minuteDiff, localTime,
-                               type = $tzSelect.val();
+                               type = oouiEnabled ? timezoneWidget.dropdowninput.getValue() : $tzSelect.val(),
+                               val = oouiEnabled ? timezoneWidget.textinput.getValue() : $tzTextbox.val();
 
                        if ( type === 'other' ) {
                                // User specified time zone manually in <input>
                                // Grab data from the textbox, parse it.
-                               minuteDiff = hoursToMinutes( $tzTextbox.val() );
+                               minuteDiff = hoursToMinutes( val );
                        } else {
                                // Time zone not manually specified by user
                                if ( type === 'guess' ) {
                                        // Get browser timezone & fill it in
                                        minuteDiff = -( new Date().getTimezoneOffset() );
-                                       $tzTextbox.val( minutesToHours( minuteDiff ) );
-                                       $tzSelect.val( 'other' );
+                                       if ( oouiEnabled ) {
+                                               timezoneWidget.textinput.setValue( minutesToHours( minuteDiff ) );
+                                               timezoneWidget.dropdowninput.setValue( 'other' );
+                                       } else {
+                                               $tzTextbox.val( minutesToHours( minuteDiff ) );
+                                               $tzSelect.val( 'other' );
+                                       }
                                } else {
-                                       // Grab data from the $tzSelect value
+                                       // Grab data from the dropdown value
                                        minuteDiff = parseInt( type.split( '|' )[ 1 ], 10 ) || 0;
                                }
                        }
                        $localtimeHolder.text( mw.language.convertNumber( minutesToHours( localTime ) ) );
                }
 
-               if ( $tzSelect.length && $tzTextbox.length ) {
-                       $tzSelect.change( updateTimezoneSelection );
-                       $tzTextbox.blur( updateTimezoneSelection );
-                       updateTimezoneSelection();
+               if ( oouiEnabled ) {
+                       if ( timezoneWidget ) {
+                               timezoneWidget.dropdowninput.on( 'change', updateTimezoneSelection );
+                               timezoneWidget.textinput.on( 'change', updateTimezoneSelection );
+                               updateTimezoneSelection();
+                       }
+               } else {
+                       if ( $tzSelect.length && $tzTextbox.length ) {
+                               $tzSelect.change( updateTimezoneSelection );
+                               $tzTextbox.blur( updateTimezoneSelection );
+                               updateTimezoneSelection();
+                       }
                }
 
        } );
index 9120e2a..144659a 100644 (file)
 
                                if ( meta && meta.tiff && meta.tiff.Orientation ) {
                                        rotation = ( 360 - ( function () {
-                                               // See includes/media/Bitmap.php
+                                               // See BitmapHandler class in PHP
                                                switch ( meta.tiff.Orientation.value ) {
                                                        case 8:
                                                                return 90;
index c92c5e5..aab1e7c 100644 (file)
 
                function updateCount() {
                        var remaining = limit - byteLength( textInputWidget.getValue() );
-                       remaining = mw.language.convertNumber( remaining );
+                       if ( remaining > 99 ) {
+                               remaining = '';
+                       } else {
+                               remaining = mw.language.convertNumber( remaining );
+                       }
                        textInputWidget.setLabel( remaining );
                }
                textInputWidget.on( 'change', updateCount );
 
                function updateCount() {
                        var remaining = limit - codePointLength( textInputWidget.getValue() );
-                       remaining = mw.language.convertNumber( remaining );
+                       if ( remaining > 99 ) {
+                               remaining = '';
+                       } else {
+                               remaining = mw.language.convertNumber( remaining );
+                       }
                        textInputWidget.setLabel( remaining );
                }
                textInputWidget.on( 'change', updateCount );
index 2e5a92e..0038ed8 100644 (file)
                 * @return {jQuery.Promise} Received token.
                 */
                getToken: function ( type, assert ) {
-                       var apiPromise, promiseGroup, d;
+                       var apiPromise, promiseGroup, d, reject;
                        type = mapLegacyToken( type );
                        promiseGroup = promises[ this.defaults.ajax.url ];
                        d = promiseGroup && promiseGroup[ type + 'Token' ];
                                        type: type,
                                        assert: assert
                                } );
+                               reject = function () {
+                                       // Clear promise. Do not cache errors.
+                                       delete promiseGroup[ type + 'Token' ];
+
+                                       // Let caller handle the error code
+                                       return $.Deferred().rejectWith( this, arguments );
+                               };
                                d = apiPromise
                                        .then( function ( res ) {
+                                               if ( !res.query ) {
+                                                       return reject( 'query-missing', res );
+                                               }
                                                // If token type is unknown, it is omitted from the response
                                                if ( !res.query.tokens[ type + 'token' ] ) {
                                                        return $.Deferred().reject( 'token-missing', res );
                                                }
-
                                                return res.query.tokens[ type + 'token' ];
-                                       }, function () {
-                                               // Clear promise. Do not cache errors.
-                                               delete promiseGroup[ type + 'Token' ];
-
-                                               // Let caller handle the error code
-                                               return $.Deferred().rejectWith( this, arguments );
-                                       } )
+                                       }, reject )
                                        // Attach abort handler
                                        .promise( { abort: apiPromise.abort } );
 
index c4363ed..57c878e 100644 (file)
        line-height: 1.4;
 }
 
+.mw-feedbackDialog-feedback-terms p:first-child {
+       margin-top: 0;
+}
+
 .mw-feedbackDialog-welcome-message {
        margin-bottom: 1em;
 }
index 67d6e2c..d81df65 100644 (file)
        };
 
        /**
-        * Returns a function suitable for use as a global, to construct strings from the message key (and optional replacements).
-        * e.g.
+        * Returns a function suitable for static use, to construct strings from a message key (and optional replacements).
+        *
+        * Example:
         *
-        *       window.gM = mediaWiki.jqueryMsg.getMessageFunction( options );
-        *       $( 'p#headline' ).html( gM( 'hello-user', username ) );
+        *       var format = mediaWiki.jqueryMsg.getMessageFunction( options );
+        *       $( '#example' ).text( format( 'hello-user', username ) );
         *
-        * Like the old gM() function this returns only strings, so it destroys any bindings. If you want to preserve bindings use the
-        * jQuery plugin version instead. This is only included for backwards compatibility with gM().
+        * Tthis returns only strings, so it destroys any bindings. If you want to preserve bindings, use the
+        * jQuery plugin version instead. This was originally created to ease migration from `window.gM()`,
+        * from a time when the parser used by `mw.message` was not extendable.
         *
         * N.B. replacements are variadic arguments or an array in second parameter. In other words:
         *    somefunction( a, b, c, d )
         *    somefunction( a, [b, c, d] )
         *
         * @param {Object} options parser options
-        * @return {Function} Function suitable for assigning to window.gM
+        * @return {Function} Function The message formatter
         * @return {string} return.key Message key.
         * @return {Array|Mixed} return.replacements Optional variable replacements (variadically or an array).
         * @return {string} return.return Rendered HTML.
                }
        };
 
-       // Deprecated! don't rely on gM existing.
-       // The window.gM ought not to be required - or if required, not required here.
-       // But moving it to extensions breaks it (?!)
-       // Need to fix plugin so it could do attributes as well, then will be okay to remove this.
-       // @deprecated since 1.23
-       mw.log.deprecate( window, 'gM', mw.jqueryMsg.getMessageFunction(), 'Use mw.message( ... ).parse() instead.' );
-
        /**
         * @method
         * @member jQuery
index 3fe276b..598293a 100644 (file)
                         *         'moduleName': {
                         *             // From mw.loader.register()
                         *             'version': '########' (hash)
-                        *             'dependencies': ['required.foo', 'bar.also', ...], (or) function () {}
+                        *             'dependencies': ['required.foo', 'bar.also', ...]
                         *             'group': 'somegroup', (or) null
                         *             'source': 'local', (or) 'anotherwiki'
                         *             'skip': 'return !!window.Example', (or) null
                                 */
                                jobs = [],
 
-                               // For getMarker()
-                               marker = null,
+                               /**
+                                * For #addEmbeddedCSS() and #addLink()
+                                *
+                                * @private
+                                * @property {HTMLElement|null} marker
+                                */
+                               marker = document.querySelector( 'meta[name="ResourceLoaderDynamicStyles"]' ),
 
                                // For addEmbeddedCSS()
                                cssBuffer = '',
                                cssBufferTimer = null,
-                               cssCallbacks = $.Callbacks(),
+                               cssCallbacks = [],
                                rAF = window.requestAnimationFrame || setTimeout;
 
-                       function getMarker() {
-                               if ( !marker ) {
-                                       // Cache
-                                       marker = document.querySelector( 'meta[name="ResourceLoaderDynamicStyles"]' );
-                                       if ( !marker ) {
-                                               mw.log( 'Created ResourceLoaderDynamicStyles marker dynamically' );
-                                               marker = document.createElement( 'meta' );
-                                               marker.name = 'ResourceLoaderDynamicStyles';
-                                               document.head.appendChild( marker );
-                                       }
-                               }
-                               return marker;
-                       }
-
                        /**
                         * Create a new style element and add it to the DOM.
                         *
                         * @private
                         * @param {string} text CSS text
-                        * @param {Node} [nextNode] The element where the style tag
+                        * @param {Node|null} [nextNode] The element where the style tag
                         *  should be inserted before
                         * @return {HTMLElement} Reference to the created style element
                         */
                        function newStyleTag( text, nextNode ) {
-                               var s = document.createElement( 'style' );
-
-                               s.appendChild( document.createTextNode( text ) );
+                               var el = document.createElement( 'style' );
+                               el.appendChild( document.createTextNode( text ) );
                                if ( nextNode && nextNode.parentNode ) {
-                                       nextNode.parentNode.insertBefore( s, nextNode );
+                                       nextNode.parentNode.insertBefore( el, nextNode );
                                } else {
-                                       document.head.appendChild( s );
+                                       document.head.appendChild( el );
                                }
-
-                               return s;
+                               return el;
                        }
 
                        /**
                         */
                        function addEmbeddedCSS( cssText, callback ) {
                                function fireCallbacks() {
-                                       var oldCallbacks = cssCallbacks;
+                                       var i,
+                                               oldCallbacks = cssCallbacks;
                                        // Reset cssCallbacks variable so it's not polluted by any calls to
                                        // addEmbeddedCSS() from one of the callbacks (T105973)
-                                       cssCallbacks = $.Callbacks();
-                                       oldCallbacks.fire().empty();
+                                       cssCallbacks = [];
+                                       for ( i = 0; i < oldCallbacks.length; i++ ) {
+                                               oldCallbacks[ i ]();
+                                       }
                                }
 
                                if ( callback ) {
-                                       cssCallbacks.add( callback );
+                                       cssCallbacks.push( callback );
                                }
 
                                // Yield once before creating the <style> tag. This lets multiple stylesheets
                                        cssBuffer = '';
                                }
 
-                               $( newStyleTag( cssText, getMarker() ) );
+                               newStyleTag( cssText, marker );
 
                                fireCallbacks();
                        }
                                        }
                                }
 
-                               // Resolves dynamic loader function and replaces it with its own results
-                               if ( typeof registry[ module ].dependencies === 'function' ) {
-                                       registry[ module ].dependencies = registry[ module ].dependencies();
-                                       // Ensures the module's dependencies are always in an array
-                                       if ( typeof registry[ module ].dependencies !== 'object' ) {
-                                               registry[ module ].dependencies = [ registry[ module ].dependencies ];
-                                       }
-                               }
                                if ( resolved.indexOf( module ) !== -1 ) {
                                        // Module already resolved; nothing to do
                                        return;
                         *
                         * @private
                         * @param {string} src URL to script, will be used as the src attribute in the script tag
-                        * @return {jQuery.Promise}
+                        * @param {Function} [callback] Callback to run after request resolution
                         */
-                       function addScript( src ) {
-                               return $.ajax( {
+                       function addScript( src, callback ) {
+                               var promise = $.ajax( {
                                        url: src,
                                        dataType: 'script',
                                        // Force jQuery behaviour to be for crossDomain. Otherwise jQuery would use
                                        crossDomain: true,
                                        cache: true
                                } );
+
+                               if ( callback ) {
+                                       promise.always( callback );
+                               }
                        }
 
                        /**
                         *
                         * @private
                         * @param {string} src URL of the script
-                        * @param {string} [moduleName] Name of currently executing module
-                        * @return {jQuery.Promise}
+                        * @param {string} moduleName Name of currently executing module
+                        * @param {Function} callback Callback to run after addScript() resolution
                         */
-                       function queueModuleScript( src, moduleName ) {
-                               var r = $.Deferred();
-
+                       function queueModuleScript( src, moduleName, callback ) {
                                pendingRequests.push( function () {
-                                       if ( moduleName && hasOwn.call( registry, moduleName ) ) {
+                                       if ( hasOwn.call( registry, moduleName ) ) {
                                                // Emulate runScript() part of execute()
                                                window.require = mw.loader.require;
                                                window.module = registry[ moduleName ].module;
                                        }
-                                       addScript( src ).always( function () {
+                                       addScript( src, function () {
                                                // 'module.exports' should not persist after the file is executed to
                                                // avoid leakage to unrelated code. 'require' should be kept, however,
                                                // as asynchronous access to 'require' is allowed and expected. (T144879)
                                                delete window.module;
-                                               r.resolve();
-
+                                               callback();
                                                // Start the next one (if any)
                                                if ( pendingRequests[ 0 ] ) {
                                                        pendingRequests.shift()();
                                        handlingPendingRequests = true;
                                        pendingRequests.shift()();
                                }
-                               return r.promise();
                        }
 
                        /**
                                // see #addEmbeddedCSS, T33676, T43331, and T49277 for details.
                                el.href = url;
 
-                               $( getMarker() ).before( el );
+                               if ( marker && marker.parentNode ) {
+                                       marker.parentNode.insertBefore( el, marker );
+                               } else {
+                                       document.head.appendChild( el );
+                               }
                        }
 
                        /**
                                                        return;
                                                }
 
-                                               queueModuleScript( arr[ i ], module ).always( function () {
+                                               queueModuleScript( arr[ i ], module, function () {
                                                        nestedAddScript( arr, callback, i + 1 );
                                                } );
                                        };
                                 *  a list of arguments compatible with this method
                                 * @param {string|number} version Module version hash (falls backs to empty string)
                                 *  Can also be a number (timestamp) for compatibility with MediaWiki 1.25 and earlier.
-                                * @param {string|Array|Function} dependencies One string or array of strings of module
-                                *  names on which this module depends, or a function that returns that array.
+                                * @param {string|Array} dependencies One string or array of strings of module
+                                *  names on which this module depends.
                                 * @param {string} [group=null] Group which the module is in
                                 * @param {string} [source='local'] Name of the source
                                 * @param {string} [skip=null] Script body of the skip function
                                        if ( typeof dependencies === 'string' ) {
                                                // A single module name
                                                deps = [ dependencies ];
-                                       } else if ( typeof dependencies === 'object' || typeof dependencies === 'function' ) {
-                                               // Array of module names or a function that returns an array
+                                       } else if ( typeof dependencies === 'object' ) {
+                                               // Array of module names
                                                deps = dependencies;
                                        }
                                        // List the module as registered
                        return $.when.apply( $, all );
                } );
                loading.then( function () {
-                       /* global mwPerformance */
-                       mwPerformance.mark( 'mwLoadEnd' );
+                       if ( window.performance && performance.mark ) {
+                               performance.mark( 'mwLoadEnd' );
+                       }
                        mw.hook( 'resourceloader.loadEnd' ).fire();
                } );
        } );
diff --git a/resources/src/mediawiki/mediawiki.sectionAnchor.css b/resources/src/mediawiki/mediawiki.sectionAnchor.css
deleted file mode 100644 (file)
index f8f0022..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-.mw-headline-anchor {
-       display: none;
-}
index d7b3f35..1db8904 100644 (file)
                switch ( mode ) {
                        case 'html5':
                                return str.replace( / /g, '_' );
-                       case 'html5-legacy':
-                               str = str.replace( /[ \t\n\r\f_'"&#%]+/g, '_' )
-                                       .replace( /^_+|_+$/, '' );
-                               if ( str === '' ) {
-                                       str = '_';
-                               }
-                               return str;
                        case 'legacy':
                                return rawurlencode( str.replace( / /g, '_' ) )
                                        .replace( /%3A/g, ':' )
                }
        };
 
-       /**
-        * @method wikiGetlink
-        * @inheritdoc #getUrl
-        * @deprecated since 1.23 Use #getUrl instead.
-        */
-       mw.log.deprecate( util, 'wikiGetlink', util.getUrl, 'Use mw.util.getUrl instead.', 'mw.util.wikiGetlink' );
-
-       /**
-        * Add the appropriate prefix to the accesskey shown in the tooltip.
-        *
-        * If the `$nodes` parameter is given, only those nodes are updated;
-        * otherwise we update all elements with accesskeys on the page.
-        *
-        * @method updateTooltipAccessKeys
-        * @param {Array|jQuery} [$nodes] A jQuery object, or array of nodes to update.
-        * @deprecated since 1.24 Use the module jquery.accessKeyLabel instead.
-        */
-       mw.log.deprecate( util, 'updateTooltipAccessKeys', function ( $nodes ) {
-               if ( !$nodes ) {
-                       $nodes = $( '[accesskey]' );
-               } else if ( !( $nodes instanceof $ ) ) {
-                       $nodes = $( $nodes );
-               }
-
-               $nodes.updateTooltipAccessKeys();
-       }, 'Use jquery.accessKeyLabel instead.', 'mw.util.updateTooltipAccessKeys' );
-
        /**
         * Add a little box at the top of the screen to inform the user of
         * something, replacing any previous message.
                return true;
        }, 'Use mw.notify instead.', 'mw.util.jsMessage' );
 
-       /**
-        * Encode the string like Sanitizer::escapeId() in PHP
-        *
-        * @method escapeId
-        * @deprecated since 1.30 use escapeIdForAttribute() or escapeIdForLink()
-        * @param {string} str String to be encoded.
-        * @return {string} Encoded string
-        */
-       mw.log.deprecate( util, 'escapeId', function ( str ) {
-               return escapeIdInternal( str, 'legacy' );
-       }, 'Use mw.util.escapeIdForAttribute or mw.util.escapeIdForLink instead.', 'mw.util.escapeId' );
-
        /**
         * Initialisation of mw.util.$content
         */
diff --git a/resources/src/moment-dmy.js b/resources/src/moment-dmy.js
deleted file mode 100644 (file)
index 2b7ca16..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-// Use DMY date format for Moment.js, in accordance with MediaWiki's date formatting routines.
-// This affects English only (and languages without localisations, that fall back to English).
-// http://momentjs.com/docs/#/customization/long-date-formats/
-/* global moment */
-moment.updateLocale( 'en', {
-       longDateFormat: {
-               // Unchanged, but have to be repeated here:
-               LT: 'h:mm A',
-               LTS: 'h:mm:ss A',
-               // Customized:
-               L: 'DD/MM/YYYY',
-               LL: 'D MMMM YYYY',
-               LLL: 'D MMMM YYYY LT',
-               LLLL: 'dddd, D MMMM YYYY LT'
-       }
-} );
diff --git a/resources/src/moment-global.js b/resources/src/moment-global.js
deleted file mode 100644 (file)
index ba01a24..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-// Back-compat: Export module as global
-window.moment = module.exports;
diff --git a/resources/src/moment-locale-overrides.js b/resources/src/moment-locale-overrides.js
deleted file mode 100644 (file)
index bafb86a..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/* global mediaWiki, moment */
-
-( function ( mw ) {
-       // HACK: Overwrite moment's i18n with MediaWiki's for the current language so that
-       // wgTranslateNumerals is respected.
-       moment.updateLocale( moment.locale(), {
-               preparse: function ( s ) {
-                       var i,
-                               table = mw.language.getDigitTransformTable();
-                       if ( mw.config.get( 'wgTranslateNumerals' ) ) {
-                               for ( i = 0; i < 10; i++ ) {
-                                       if ( table[ i ] !== undefined ) {
-                                               s = s.replace( new RegExp( mw.RegExp.escape( table[ i ] ), 'g' ), i );
-                                       }
-                               }
-                       }
-                       // HACK: momentjs replaces commas in some languages, which is the only other use of preparse
-                       // aside from digit transformation. We can only override preparse, not extend it, so we
-                       // have to replicate the comma replacement functionality here.
-                       if ( [ 'ar', 'ar-sa', 'fa' ].indexOf( mw.config.get( 'wgUserLanguage' ) ) !== -1 ) {
-                               s = s.replace( /،/g, ',' );
-                       }
-                       return s;
-               },
-               postformat: function ( s ) {
-                       var i,
-                               table = mw.language.getDigitTransformTable();
-                       if ( mw.config.get( 'wgTranslateNumerals' ) ) {
-                               for ( i = 0; i < 10; i++ ) {
-                                       if ( table[ i ] !== undefined ) {
-                                               s = s.replace( new RegExp( i, 'g' ), table[ i ] );
-                                       }
-                               }
-                       }
-                       // HACK: momentjs replaces commas in some languages, which is the only other use of postformat
-                       // aside from digit transformation. We can only override postformat, not extend it, so we
-                       // have to replicate the comma replacement functionality here.
-                       if ( [ 'ar', 'ar-sa', 'fa' ].indexOf( mw.config.get( 'wgUserLanguage' ) ) !== -1 ) {
-                               s = s.replace( /,/g, '،' );
-                       }
-                       return s;
-               }
-       } );
-}( mediaWiki ) );
diff --git a/resources/src/moment/moment-dmy.js b/resources/src/moment/moment-dmy.js
new file mode 100644 (file)
index 0000000..2b7ca16
--- /dev/null
@@ -0,0 +1,16 @@
+// Use DMY date format for Moment.js, in accordance with MediaWiki's date formatting routines.
+// This affects English only (and languages without localisations, that fall back to English).
+// http://momentjs.com/docs/#/customization/long-date-formats/
+/* global moment */
+moment.updateLocale( 'en', {
+       longDateFormat: {
+               // Unchanged, but have to be repeated here:
+               LT: 'h:mm A',
+               LTS: 'h:mm:ss A',
+               // Customized:
+               L: 'DD/MM/YYYY',
+               LL: 'D MMMM YYYY',
+               LLL: 'D MMMM YYYY LT',
+               LLLL: 'dddd, D MMMM YYYY LT'
+       }
+} );
diff --git a/resources/src/moment/moment-global.js b/resources/src/moment/moment-global.js
new file mode 100644 (file)
index 0000000..ba01a24
--- /dev/null
@@ -0,0 +1,2 @@
+// Back-compat: Export module as global
+window.moment = module.exports;
diff --git a/resources/src/moment/moment-locale-overrides.js b/resources/src/moment/moment-locale-overrides.js
new file mode 100644 (file)
index 0000000..bafb86a
--- /dev/null
@@ -0,0 +1,44 @@
+/* global mediaWiki, moment */
+
+( function ( mw ) {
+       // HACK: Overwrite moment's i18n with MediaWiki's for the current language so that
+       // wgTranslateNumerals is respected.
+       moment.updateLocale( moment.locale(), {
+               preparse: function ( s ) {
+                       var i,
+                               table = mw.language.getDigitTransformTable();
+                       if ( mw.config.get( 'wgTranslateNumerals' ) ) {
+                               for ( i = 0; i < 10; i++ ) {
+                                       if ( table[ i ] !== undefined ) {
+                                               s = s.replace( new RegExp( mw.RegExp.escape( table[ i ] ), 'g' ), i );
+                                       }
+                               }
+                       }
+                       // HACK: momentjs replaces commas in some languages, which is the only other use of preparse
+                       // aside from digit transformation. We can only override preparse, not extend it, so we
+                       // have to replicate the comma replacement functionality here.
+                       if ( [ 'ar', 'ar-sa', 'fa' ].indexOf( mw.config.get( 'wgUserLanguage' ) ) !== -1 ) {
+                               s = s.replace( /،/g, ',' );
+                       }
+                       return s;
+               },
+               postformat: function ( s ) {
+                       var i,
+                               table = mw.language.getDigitTransformTable();
+                       if ( mw.config.get( 'wgTranslateNumerals' ) ) {
+                               for ( i = 0; i < 10; i++ ) {
+                                       if ( table[ i ] !== undefined ) {
+                                               s = s.replace( new RegExp( i, 'g' ), table[ i ] );
+                                       }
+                               }
+                       }
+                       // HACK: momentjs replaces commas in some languages, which is the only other use of postformat
+                       // aside from digit transformation. We can only override postformat, not extend it, so we
+                       // have to replicate the comma replacement functionality here.
+                       if ( [ 'ar', 'ar-sa', 'fa' ].indexOf( mw.config.get( 'wgUserLanguage' ) ) !== -1 ) {
+                               s = s.replace( /,/g, '،' );
+                       }
+                       return s;
+               }
+       } );
+}( mediaWiki ) );
index cc313c7..41bcbaa 100644 (file)
@@ -5,11 +5,8 @@
  * - Beware: Do not call mwNow before the isCompatible() check.
  */
 
-/* global mw, mwPerformance, mwNow, isCompatible, $VARS, $CODE */
+/* global mw, mwNow, isCompatible, $VARS, $CODE */
 
-window.mwPerformance = ( window.performance && performance.mark ) ? performance : {
-       mark: function () {}
-};
 // Define now() here to ensure valid comparison with mediaWikiLoadEnd (T153819).
 window.mwNow = ( function () {
        var perf = window.performance,
@@ -151,8 +148,9 @@ window.isCompatible = function ( str ) {
        }
 
        window.mediaWikiLoadStart = mwNow();
-       mwPerformance.mark( 'mwLoadStart' );
-
+       if ( window.performance && performance.mark ) {
+               performance.mark( 'mwStartup' );
+       }
        script = document.createElement( 'script' );
        script.src = $VARS.baseModulesUri;
        script.onload = function () {
index 91ddf24..08ec9f6 100644 (file)
@@ -271,7 +271,6 @@ class ParserTestRunner {
                $setup['wgNoFollowLinks'] = true;
                $setup['wgNoFollowDomainExceptions'] = [ 'no-nofollow.org' ];
                $setup['wgExternalLinkTarget'] = false;
-               $setup['wgExperimentalHtmlIds'] = false;
                $setup['wgLocaltimezone'] = 'UTC';
                $setup['wgHtml5'] = true;
                $setup['wgDisableLangConversion'] = false;
@@ -1244,7 +1243,7 @@ class ParserTestRunner {
                $teardown[] = $this->markSetupDone( 'setupDatabase' );
 
                # CREATE TEMPORARY TABLE breaks if there is more than one server
-               if ( wfGetLB()->getServerCount() != 1 ) {
+               if ( MediaWikiServices::getInstance()->getDBLoadBalancer()->getServerCount() != 1 ) {
                        $this->useTemporaryTables = false;
                }
 
index 71ebd6f..5ea72b2 100644 (file)
@@ -72,7 +72,6 @@ return [
                'maintenance/',
                'mw-config/',
                'resources/',
-               'skins/',
                'vendor/',
        ],
 
@@ -100,8 +99,6 @@ return [
                'maintenance/language/',
                // External class
                'includes/libs/jsminplus.php',
-               // separate repositories
-               'skins/',
        ],
 
        /**
index 2bde97b..583b751 100644 (file)
@@ -43,6 +43,8 @@ class MovePageTest extends MediaWikiTestCase {
        /**
         * Integration test to catch regressions like T74870. Taken and modified
         * from SemanticMediaWiki
+        *
+        * @covers Title::moveTo
         */
        public function testTitleMoveCompleteIntegrationTest() {
                $oldTitle = Title::newFromText( 'Help:Some title' );
index a5a7364..91655ea 100644 (file)
@@ -151,6 +151,7 @@ class OutputPageTest extends MediaWikiTestCase {
 
        /**
         * @dataProvider provideCdnCacheEpoch
+        * @covers OutputPage::getCdnCacheEpoch
         */
        public function testCdnCacheEpoch( $params ) {
                $out = TestingAccessWrapper::newFromObject( $this->newInstance() );
index 3d74ae3..4747466 100644 (file)
@@ -1,5 +1,8 @@
 <?php
 
+/**
+ * @coversNothing Just a sample
+ */
 class SampleTest extends MediaWikiLangTestCase {
 
        /**
index 7d6906c..2a92956 100644 (file)
@@ -1259,7 +1259,8 @@ class RevisionStoreDbTest extends MediaWikiTestCase {
         */
        public function testNewMutableRevisionFromArray_legacyEncoding( array $array ) {
                $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
-               $blobStore = new SqlBlobStore( wfGetLB(), $cache );
+               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+               $blobStore = new SqlBlobStore( $lb, $cache );
                $blobStore->setLegacyEncoding( 'windows-1252', Language::factory( 'en' ) );
 
                $factory = $this->getMockBuilder( BlobStoreFactory::class )
index 0bce572..fed9a0c 100644 (file)
@@ -614,11 +614,12 @@ class RevisionStoreTest extends MediaWikiTestCase {
         */
        public function testNewRevisionFromRow_legacyEncoding_applied( $encoding, $locale, $row, $text ) {
                $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
+               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
 
-               $blobStore = new SqlBlobStore( wfGetLB(), $cache );
+               $blobStore = new SqlBlobStore( $lb, $cache );
                $blobStore->setLegacyEncoding( $encoding, Language::factory( $locale ) );
 
-               $store = $this->getRevisionStore( wfGetLB(), $blobStore, $cache );
+               $store = $this->getRevisionStore( $lb, $blobStore, $cache );
 
                $record = $store->newRevisionFromRow(
                        $this->makeRow( $row ),
@@ -640,11 +641,12 @@ class RevisionStoreTest extends MediaWikiTestCase {
                ];
 
                $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
+               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
 
-               $blobStore = new SqlBlobStore( wfGetLB(), $cache );
+               $blobStore = new SqlBlobStore( $lb, $cache );
                $blobStore->setLegacyEncoding( 'windows-1252', Language::factory( 'en' ) );
 
-               $store = $this->getRevisionStore( wfGetLB(), $blobStore, $cache );
+               $store = $this->getRevisionStore( $lb, $blobStore, $cache );
 
                $record = $store->newRevisionFromRow(
                        $this->makeRow( $row ),
index a04271f..8a40266 100644 (file)
@@ -133,6 +133,7 @@ class ApiParseTest extends ApiTestCase {
                                ->getMock();
                        $skin->expects( $this->once() )->method( 'getDefaultModules' )
                                ->willReturn( [
+                                       'styles' => [ 'core' => [ 'quux.styles' ] ],
                                        'core' => [ 'foo', 'bar' ],
                                        'content' => [ 'baz' ]
                                ] );
@@ -686,7 +687,7 @@ class ApiParseTest extends ApiTestCase {
                        'resp.parse.modulescripts'
                );
                $this->assertSame(
-                       [ 'foo.styles' ],
+                       [ 'foo.styles', 'quux.styles' ],
                        $res[0]['parse']['modulestyles'],
                        'resp.parse.modulestyles'
                );
index 1a7ed12..ff22def 100644 (file)
@@ -5,6 +5,9 @@ namespace MediaWiki\Auth;
 use Psr\Log\LoggerInterface;
 use Wikimedia\TestingAccessWrapper;
 
+/**
+ * @covers \MediaWiki\Auth\EmailNotificationSecondaryAuthenticationProvider
+ */
 class EmailNotificationSecondaryAuthenticationProviderTest extends \PHPUnit\Framework\TestCase {
        public function testConstructor() {
                $config = new \HashConfig( [
index ed4c977..b715b1f 100644 (file)
@@ -157,12 +157,18 @@ class LBFactoryTest extends MediaWikiTestCase {
                global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
 
                $factory = new LBFactoryMulti( [
-                       'sectionsByDB' => [],
+                       'sectionsByDB' => [
+                               's1wiki' => 's1',
+                       ],
                        'sectionLoads' => [
+                               's1' => [
+                                       'test-db3' => 0,
+                                       'test-db4' => 100,
+                               ],
                                'DEFAULT' => [
                                        'test-db1' => 0,
                                        'test-db2' => 100,
-                               ],
+                               ]
                        ],
                        'serverTemplate' => [
                                'dbname'      => $wgDBname,
@@ -174,7 +180,9 @@ class LBFactoryTest extends MediaWikiTestCase {
                        ],
                        'hostsByName' => [
                                'test-db1'  => $wgDBserver,
-                               'test-db2'  => $wgDBserver
+                               'test-db2'  => $wgDBserver,
+                               'test-db3'  => $wgDBserver,
+                               'test-db4'  => $wgDBserver
                        ],
                        'loadMonitorClass' => LoadMonitorNull::class
                ] );
@@ -186,8 +194,25 @@ class LBFactoryTest extends MediaWikiTestCase {
                $dbr = $lb->getConnection( DB_REPLICA );
                $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'slave shows as slave' );
 
+               // Test that LoadBalancer instances made during commitMasterChanges() do not throw
+               // DBTransactionError due to transaction ROUND_* stages being mismatched.
+               $factory->beginMasterChanges( __METHOD__ );
+               $dbw->onTransactionPreCommitOrIdle( function () use ( $factory ) {
+                       // Trigger s1 LoadBalancer instantiation during "finalize" stage.
+                       // There is no s1wiki DB to select so it is not in getConnection(),
+                       // but this fools getMainLB() at least.
+                       $factory->getMainLB( 's1wiki' )->getConnection( DB_MASTER );
+               } );
+               $factory->commitMasterChanges( __METHOD__ );
+
+               $count = 0;
+               $factory->forEachLB( function () use ( &$count ) {
+                       ++$count;
+               } );
+               $this->assertEquals( 2, $count );
+
                $factory->shutdown();
-               $lb->closeAll();
+               $factory->closeAll();
        }
 
        /**
index e054569..7462f1d 100644 (file)
@@ -302,4 +302,91 @@ class LoadBalancerTest extends MediaWikiTestCase {
 
                $lb->closeAll();
        }
+
+       public function testTransactionCallbackChains() {
+               global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
+
+               $servers = [
+                       [
+                               'host' => $wgDBserver,
+                               'dbname' => $wgDBname,
+                               'tablePrefix' => $this->dbPrefix(),
+                               'user' => $wgDBuser,
+                               'password' => $wgDBpassword,
+                               'type' => $wgDBtype,
+                               'dbDirectory' => $wgSQLiteDataDir,
+                               'load' => 0,
+                               'flags' => DBO_TRX // REPEATABLE-READ for consistency
+                       ],
+               ];
+
+               $lb = new LoadBalancer( [
+                       'servers' => $servers,
+                       'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
+               ] );
+
+               $conn1 = $lb->openConnection( $lb->getWriterIndex(), false );
+               $conn2 = $lb->openConnection( $lb->getWriterIndex(), '' );
+
+               $count = 0;
+               $lb->forEachOpenMasterConnection( function () use ( &$count ) {
+                       ++$count;
+               } );
+               $this->assertEquals( 2, $count, 'Connection handle count' );
+
+               $tlCalls = 0;
+               $lb->setTransactionListener( 'test-listener', function () use ( &$tlCalls ) {
+                       ++$tlCalls;
+               } );
+
+               $lb->beginMasterChanges( __METHOD__ );
+               $bc = array_fill_keys( [ 'a', 'b', 'c', 'd' ], 0 );
+               $conn1->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
+                       $bc['a'] = 1;
+                       $conn2->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
+                               $bc['b'] = 1;
+                               $conn1->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
+                                       $bc['c'] = 1;
+                                       $conn1->onTransactionPreCommitOrIdle( function () use ( &$bc, $conn1, $conn2 ) {
+                                               $bc['d'] = 1;
+                                       } );
+                               } );
+                       } );
+               } );
+               $lb->finalizeMasterChanges();
+               $lb->approveMasterChanges( [] );
+               $lb->commitMasterChanges( __METHOD__ );
+               $lb->runMasterTransactionIdleCallbacks();
+               $lb->runMasterTransactionListenerCallbacks();
+
+               $this->assertEquals( array_fill_keys( [ 'a', 'b', 'c', 'd' ], 1 ), $bc );
+               $this->assertEquals( 2, $tlCalls );
+
+               $tlCalls = 0;
+               $lb->beginMasterChanges( __METHOD__ );
+               $ac = array_fill_keys( [ 'a', 'b', 'c', 'd' ], 0 );
+               $conn1->onTransactionIdle( function () use ( &$ac, $conn1, $conn2 ) {
+                       $ac['a'] = 1;
+                       $conn2->onTransactionIdle( function () use ( &$ac, $conn1, $conn2 ) {
+                               $ac['b'] = 1;
+                               $conn1->onTransactionIdle( function () use ( &$ac, $conn1, $conn2 ) {
+                                       $ac['c'] = 1;
+                                       $conn1->onTransactionIdle( function () use ( &$ac, $conn1, $conn2 ) {
+                                               $ac['d'] = 1;
+                                       } );
+                               } );
+                       } );
+               } );
+               $lb->finalizeMasterChanges();
+               $lb->approveMasterChanges( [] );
+               $lb->commitMasterChanges( __METHOD__ );
+               $lb->runMasterTransactionIdleCallbacks();
+               $lb->runMasterTransactionListenerCallbacks();
+
+               $this->assertEquals( array_fill_keys( [ 'a', 'b', 'c', 'd' ], 1 ), $ac );
+               $this->assertEquals( 2, $tlCalls );
+
+               $conn1->close();
+               $conn2->close();
+       }
 }
index 6b41707..a1e41d9 100644 (file)
@@ -335,4 +335,42 @@ class DeferredUpdatesTest extends MediaWikiTestCase {
 
                $this->assertSame( 1, $ran, 'Update ran' );
        }
+
+       /**
+        * @covers DeferredUpdates::tryOpportunisticExecute
+        */
+       public function testTryOpportunisticExecute() {
+               $calls = [];
+               $callback1 = function () use ( &$calls ) {
+                       $calls[] = 1;
+               };
+               $callback2 = function () use ( &$calls ) {
+                       $calls[] = 2;
+               };
+
+               $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               $lbFactory->beginMasterChanges( __METHOD__ );
+
+               DeferredUpdates::addCallableUpdate( $callback1 );
+               $this->assertEquals( [], $calls );
+
+               DeferredUpdates::tryOpportunisticExecute( 'run' );
+               $this->assertEquals( [], $calls );
+
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->onTransactionIdle( function () use ( &$calls, $callback2 ) {
+                       DeferredUpdates::addCallableUpdate( $callback2 );
+                       $this->assertEquals( [], $calls );
+                       $calls[] = 'oti';
+               } );
+               $this->assertEquals( 1, $dbw->trxLevel() );
+               $this->assertEquals( [], $calls );
+
+               $lbFactory->commitMasterChanges( __METHOD__ );
+
+               $this->assertEquals( [ 'oti' ], $calls );
+
+               DeferredUpdates::tryOpportunisticExecute( 'run' );
+               $this->assertEquals( [ 'oti', 1, 2 ], $calls );
+       }
 }
index 46bf2c6..dabf66b 100644 (file)
@@ -316,6 +316,10 @@ class CSSMinTest extends MediaWikiTestCase {
                                [ 'background-image: url("");', false, '/example', false ],
                                'background-image: url("");',
                        ],
+                       'Single quote with outer spacing' => [
+                               [ "background-image: url( '' );", false, '/example', false ],
+                               "background-image: url( '' );",
+                       ],
                ];
        }
 
@@ -390,6 +394,11 @@ class CSSMinTest extends MediaWikiTestCase {
                                'foo { background: url(/static/foo.png?query=yes); }',
                                'foo { background: url(https://expand.example/static/foo.png?query=yes); }',
                        ],
+                       [
+                               'Path-relative URL with query',
+                               "foo { background: url(?query=yes); }",
+                               'foo { background: url(http://localhost/w/?query=yes); }',
+                       ],
                        [
                                'Remote URL (unnecessary quotes not preserved)',
                                'foo { background: url("http://example.org/w/unnecessary-quotes.png"); }',
@@ -534,6 +543,11 @@ class CSSMinTest extends MediaWikiTestCase {
                                'foo { background: url( "http://localhost/styles.css?quoted=double" ) }',
                                'foo { background: url(http://localhost/styles.css?quoted=double) }',
                        ],
+                       [
+                               'Background URL (single quoted, containing spaces, with outer spacing)',
+                               "foo { background: url( ' red.gif ' ); }",
+                               'foo { background: url("http://localhost/w/ red.gif "); }',
+                       ],
                        [
                                'Simple case with comments before url',
                                'foo { prop: /* some {funny;} comment */ url(bar.png); }',
index 3335a2b..f08b376 100644 (file)
@@ -9,6 +9,7 @@ use Wikimedia\TestingAccessWrapper;
 use Wikimedia\Rdbms\DatabaseSqlite;
 use Wikimedia\Rdbms\DatabasePostgres;
 use Wikimedia\Rdbms\DatabaseMssql;
+use Wikimedia\Rdbms\DBUnexpectedError;
 
 class DatabaseTest extends PHPUnit\Framework\TestCase {
 
@@ -367,7 +368,7 @@ class DatabaseTest extends PHPUnit\Framework\TestCase {
                        $called = true;
                        $db->setFlag( DBO_TRX );
                } );
-               $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
+               $db->rollback( __METHOD__ );
                $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
                $this->assertTrue( $called, 'Callback reached' );
        }
@@ -489,37 +490,56 @@ class DatabaseTest extends PHPUnit\Framework\TestCase {
                $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
                $db->clearFlag( DBO_TRX );
 
+               // Pending writes with DBO_TRX
                $this->assertEquals( 0, $db->trxLevel() );
-
+               $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
                $db->setFlag( DBO_TRX );
+               $db->query( "DELETE FROM test WHERE t = 1" ); // trigger DBO_TRX transaction before lock
                try {
-                       $this->badLockingMethodImplicit( $db );
-               } catch ( RunTimeException $e ) {
-                       $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
+                       $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
+                       $this->fail( "Exception not reached" );
+               } catch ( DBUnexpectedError $e ) {
+                       $this->assertEquals( 1, $db->trxLevel(), "Transaction not committed." );
+                       $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ), 'Lock not acquired' );
                }
-               $db->clearFlag( DBO_TRX );
                $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
-               $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
-
+               // Pending writes without DBO_TRX
+               $db->clearFlag( DBO_TRX );
+               $this->assertEquals( 0, $db->trxLevel() );
+               $this->assertTrue( $db->lockIsFree( 'meow2', __METHOD__ ) );
+               $db->begin( __METHOD__ );
+               $db->query( "DELETE FROM test WHERE t = 1" ); // trigger DBO_TRX transaction before lock
                try {
-                       $this->badLockingMethodExplicit( $db );
-               } catch ( RunTimeException $e ) {
-                       $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
+                       $lock = $db->getScopedLockAndFlush( 'meow2', __METHOD__, 1 );
+                       $this->fail( "Exception not reached" );
+               } catch ( DBUnexpectedError $e ) {
+                       $this->assertEquals( 1, $db->trxLevel(), "Transaction not committed." );
+                       $this->assertTrue( $db->lockIsFree( 'meow2', __METHOD__ ), 'Lock not acquired' );
                }
+               $db->rollback( __METHOD__ );
+               // No pending writes, with DBO_TRX
+               $db->setFlag( DBO_TRX );
+               $this->assertEquals( 0, $db->trxLevel() );
+               $this->assertTrue( $db->lockIsFree( 'wuff', __METHOD__ ) );
+               $db->query( "SELECT 1", __METHOD__ );
+               $this->assertEquals( 1, $db->trxLevel() );
+               $lock = $db->getScopedLockAndFlush( 'wuff', __METHOD__, 1 );
+               $this->assertEquals( 0, $db->trxLevel() );
+               $this->assertFalse( $db->lockIsFree( 'wuff', __METHOD__ ), 'Lock already acquired' );
                $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
-               $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
-       }
-
-       private function badLockingMethodImplicit( IDatabase $db ) {
-               $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
-               $db->query( "SELECT 1" ); // trigger DBO_TRX
-               throw new RunTimeException( "Uh oh!" );
-       }
-
-       private function badLockingMethodExplicit( IDatabase $db ) {
-               $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
+               // No pending writes, without DBO_TRX
+               $db->clearFlag( DBO_TRX );
+               $this->assertEquals( 0, $db->trxLevel() );
+               $this->assertTrue( $db->lockIsFree( 'wuff2', __METHOD__ ) );
                $db->begin( __METHOD__ );
-               throw new RunTimeException( "Uh oh!" );
+               try {
+                       $lock = $db->getScopedLockAndFlush( 'wuff2', __METHOD__, 1 );
+                       $this->fail( "Exception not reached" );
+               } catch ( DBUnexpectedError $e ) {
+                       $this->assertEquals( 1, $db->trxLevel(), "Transaction not committed." );
+                       $this->assertFalse( $db->lockIsFree( 'wuff2', __METHOD__ ), 'Lock not acquired' );
+               }
+               $db->rollback( __METHOD__ );
        }
 
        /**
index e523a31..91ce9ea 100644 (file)
@@ -91,10 +91,11 @@ class LogFormatterTest extends MediaWikiLangTestCase {
 
                $formatter->setShowUserToolLinks( false );
                $paramsWithoutTools = $formatter->getMessageParametersForTesting();
-               unset( $formatter->parsedParameters );
 
-               $formatter->setShowUserToolLinks( true );
-               $paramsWithTools = $formatter->getMessageParametersForTesting();
+               $formatter2 = LogFormatter::newFromEntry( $entry );
+               $formatter2->setContext( $this->context );
+               $formatter2->setShowUserToolLinks( true );
+               $paramsWithTools = $formatter2->getMessageParametersForTesting();
 
                $userLink = Linker::userLink(
                        $this->user->getId(),
index 6590338..b5965c4 100644 (file)
@@ -457,7 +457,6 @@ class SanitizerTest extends MediaWikiTestCase {
                $legacyEncoded = 'foo_.D1.82.D0.B5.D1.81.D1.82_.23.25.21.27.28.29.5B.5D:.3C.3E' .
                        '.26.26amp.3B.26amp.3Bamp.3B';
                $html5Encoded = 'foo_тест_#%!\'()[]:<>&&amp;&amp;amp;';
-               $html5Experimental = 'foo_тест_!_()[]:<>_amp;_amp;amp;';
 
                // Settings: last element is $wgExternalInterwikiFragmentMode, the rest is $wgFragmentMode
                $legacy = [ 'legacy', 'legacy' ];
@@ -465,8 +464,6 @@ class SanitizerTest extends MediaWikiTestCase {
                $newLegacy = [ 'html5', 'legacy', 'legacy' ];
                $new = [ 'html5', 'legacy' ];
                $allNew = [ 'html5', 'html5' ];
-               $experimentalLegacy = [ 'html5-legacy', 'legacy', 'legacy' ];
-               $newExperimental = [ 'html5', 'html5-legacy', 'legacy' ];
 
                return [
                        // Pure legacy: how MW worked before 2017
@@ -498,18 +495,6 @@ class SanitizerTest extends MediaWikiTestCase {
                        [ 'Attribute', $allNew, $text, false, Sanitizer::ID_FALLBACK ],
                        [ 'Link', $allNew, $text, $html5Encoded ],
                        [ 'ExternalInterwiki', $allNew, $text, $html5Encoded ],
-
-                       // Someone flipped $wgExperimentalHtmlIds on
-                       [ 'Attribute', $experimentalLegacy, $text, $html5Experimental, Sanitizer::ID_PRIMARY ],
-                       [ 'Attribute', $experimentalLegacy, $text, $legacyEncoded, Sanitizer::ID_FALLBACK ],
-                       [ 'Link', $experimentalLegacy, $text, $html5Experimental ],
-                       [ 'ExternalInterwiki', $experimentalLegacy, $text, $legacyEncoded ],
-
-                       // Migration from $wgExperimentalHtmlIds to modern HTML5
-                       [ 'Attribute', $newExperimental, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
-                       [ 'Attribute', $newExperimental, $text, $html5Experimental, Sanitizer::ID_FALLBACK ],
-                       [ 'Link', $newExperimental, $text, $html5Encoded ],
-                       [ 'ExternalInterwiki', $newExperimental, $text, $legacyEncoded ],
                ];
        }
 
index c101523..62ab44c 100644 (file)
@@ -117,7 +117,7 @@ class DefaultPreferencesFactoryTest extends MediaWikiTestCase {
                $configMock = new HashConfig( [
                        'HiddenPrefs' => []
                ] );
-               $form = $this->getMockBuilder( PreferencesForm::class )
+               $form = $this->getMockBuilder( PreferencesFormLegacy::class )
                        ->disableOriginalConstructor()
                        ->getMock();
 
@@ -170,6 +170,8 @@ class DefaultPreferencesFactoryTest extends MediaWikiTestCase {
 
        /**
         * The rclimit preference should accept non-integer input and filter it to become an integer.
+        *
+        * @covers \MediaWiki\Preferences\DefaultPreferencesFactory::saveFormData
         */
        public function testIntvalFilter() {
                // Test a string with leading zeros (i.e. not octal) and spaces.
index a1b1422..61ab8a5 100644 (file)
@@ -71,8 +71,7 @@ CSS
 
        /**
         * @dataProvider provideGetStyles
-        * @covers ResourceLoaderSkinModule::normalizeStyles
-        * @covers ResourceLoaderSkinModule::getStyles
+        * @covers ResourceLoaderSkinModule
         */
        public function testGetStyles( $parent, $logo, $expected ) {
                $module = $this->getMockBuilder( ResourceLoaderSkinModule::class )
index 4e9f539..e811d87 100644 (file)
@@ -99,6 +99,22 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
                $resourceLoader->register( 'test', new stdClass() );
        }
 
+       /**
+        * @covers ResourceLoader::register
+        */
+       public function testRegisterDuplicate() {
+               $logger = $this->getMockBuilder( Psr\Log\LoggerInterface::class )->getMock();
+               $logger->expects( $this->once() )
+                       ->method( 'warning' );
+               $resourceLoader = new EmptyResourceLoader( null, $logger );
+
+               $module1 = new ResourceLoaderTestModule();
+               $module2 = new ResourceLoaderTestModule();
+               $resourceLoader->register( 'test', $module1 );
+               $resourceLoader->register( 'test', $module2 );
+               $this->assertSame( $module2, $resourceLoader->getModule( 'test' ) );
+       }
+
        /**
         * @covers ResourceLoader::getModuleNames
         */
index 7c16f6c..da6e9f9 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Tests for the DBSiteStore class.
  *
@@ -37,7 +39,8 @@ class DBSiteStoreTest extends MediaWikiTestCase {
        private function newDBSiteStore() {
                // NOTE: Use the real DB load balancer for now. Eventually, the test framework should
                // provide a LoadBalancer that is safe to use in unit tests.
-               return new DBSiteStore( wfGetLB() );
+               $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+               return new DBSiteStore( $lb );
        }
 
        /**
index 06b0667..6ea5b40 100644 (file)
@@ -49,53 +49,61 @@ class SkinTemplateTest extends MediaWikiTestCase {
                $mock->expects( $this->once() )
                        ->method( 'isSyndicated' )
                        ->will( $this->returnValue( $isSyndicated ) );
-               $mock->expects( $this->once() )
+               $mock->expects( $this->any() )
                        ->method( 'getHTML' )
                        ->will( $this->returnValue( $html ) );
                return $mock;
        }
 
-       public function provideSetupSkinUserCss() {
+       public function provideGetDefaultModules() {
                $defaultStyles = [
                        'mediawiki.legacy.shared',
                        'mediawiki.legacy.commonPrint',
-                       'mediawiki.sectionAnchor',
                ];
                $buttonStyle = 'mediawiki.ui.button';
                $feedStyle = 'mediawiki.feedlink';
                return [
                        [
-                               $this->getMockOutputPage( false, '' ),
+                               false,
+                               '',
                                $defaultStyles
                        ],
                        [
-                               $this->getMockOutputPage( true, '' ),
+                               true,
+                               '',
                                array_merge( $defaultStyles, [ $feedStyle ] )
                        ],
                        [
-                               $this->getMockOutputPage( false, 'FOO mw-ui-button BAR' ),
+                               false,
+                               'FOO mw-ui-button BAR',
                                array_merge( $defaultStyles, [ $buttonStyle ] )
                        ],
                        [
-                               $this->getMockOutputPage( true, 'FOO mw-ui-button BAR' ),
-                               array_merge( $defaultStyles, [ $feedStyle, $buttonStyle ] )
+                               true,
+                               'FOO mw-ui-button BAR',
+                               array_merge( $defaultStyles, [ $buttonStyle, $feedStyle ] )
                        ],
                ];
        }
 
        /**
-        * @param PHPUnit_Framework_MockObject_MockObject|OutputPage $outputPageMock
-        * @param string[] $expectedModuleStyles
-        *
-        * @covers SkinTemplate::setupSkinUserCss
-        * @dataProvider provideSetupSkinUserCss
+        * @covers Skin::getDefaultModules
+        * @dataProvider provideGetDefaultModules
         */
-       public function testSetupSkinUserCss( $outputPageMock, $expectedModuleStyles ) {
-               $outputPageMock->expects( $this->once() )
-                       ->method( 'addModuleStyles' )
-                       ->with( $expectedModuleStyles );
+       public function testgetDefaultModules( $isSyndicated, $html, $expectedModuleStyles ) {
+               $skin = new SkinTemplate();
 
-               $skinTemplate = new SkinTemplate();
-               $skinTemplate->setupSkinUserCss( $outputPageMock );
+               $context = new DerivativeContext( $skin->getContext() );
+               $context->setOutput( $this->getMockOutputPage( $isSyndicated, $html ) );
+               $skin->setContext( $context );
+
+               $modules = $skin->getDefaultModules();
+
+               $actualStylesModule = call_user_func_array( 'array_merge', $modules['styles'] );
+               $this->assertArraySubset(
+                       $expectedModuleStyles,
+                       $actualStylesModule,
+                       'style modules'
+               );
        }
 }
diff --git a/tests/phpunit/includes/skins/SkinTest.php b/tests/phpunit/includes/skins/SkinTest.php
new file mode 100644 (file)
index 0000000..41ef2b7
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+class SkinTest extends MediaWikiTestCase {
+
+       /**
+        * @covers Skin::getDefaultModules
+        */
+       public function testGetDefaultModules() {
+               $skin = $this->getMockBuilder( Skin::class )
+                       ->setMethods( [ 'outputPage', 'setupSkinUserCss' ] )
+                       ->getMock();
+
+               $modules = $skin->getDefaultModules();
+               $this->assertTrue( isset( $modules['core'] ), 'core key is set by default' );
+               $this->assertTrue( isset( $modules['styles'] ), 'style key is set by default' );
+       }
+}
index 05a63db..69fa0dd 100644 (file)
@@ -33,7 +33,7 @@ class SpecialEditWatchlistTest extends SpecialPageTestBase {
                $user = new TestUser( __METHOD__ );
                list( $html, ) = $this->executeSpecialPage( 'clear', null, 'qqx', $user->getUser() );
                $this->assertRegExp(
-                       '/<form class="mw-htmlform" action=".*?Special:EditWatchlist\/clear" method="post">/',
+                       '/<form action=\'.*?Special:EditWatchlist\/clear\'/',
                        $html
                );
        }
@@ -42,7 +42,7 @@ class SpecialEditWatchlistTest extends SpecialPageTestBase {
                $user = new TestUser( __METHOD__ );
                list( $html, ) = $this->executeSpecialPage( 'raw', null, 'qqx', $user->getUser() );
                $this->assertContains(
-                       '<textarea id="mw-input-wpTitles"',
+                       '<div id=\'mw-input-wpTitles\'',
                        $html
                );
        }
diff --git a/tests/phpunit/includes/tidy/BalancerTest.php b/tests/phpunit/includes/tidy/BalancerTest.php
deleted file mode 100644 (file)
index 8a4f662..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-<?php
-
-class BalancerTest extends MediaWikiTestCase {
-
-       /**
-        * Anything that needs to happen before your tests should go here.
-        */
-       protected function setUp() {
-               // Be sure to do call the parent setup and teardown functions.
-               // This makes sure that all the various cleanup and restorations
-               // happen as they should (including the restoration for setMwGlobals).
-               parent::setUp();
-       }
-
-       /**
-        * @covers MediaWiki\Tidy\Balancer
-        * @covers MediaWiki\Tidy\BalanceSets
-        * @covers MediaWiki\Tidy\BalanceElement
-        * @covers MediaWiki\Tidy\BalanceStack
-        * @covers MediaWiki\Tidy\BalanceMarker
-        * @covers MediaWiki\Tidy\BalanceActiveFormattingElements
-        * @dataProvider provideBalancerTests
-        */
-       public function testBalancer( $description, $input, $expected, $useTidy ) {
-               $balancer = new MediaWiki\Tidy\Balancer( [
-                       'strict' => false, /* not strict */
-                       'allowedHtmlElements' => null, /* no sanitization */
-                       'tidyCompat' => $useTidy, /* standard parser */
-                       'allowComments' => true, /* comment parsing */
-               ] );
-               $output = $balancer->balance( $input );
-
-               // Ignore self-closing tags
-               $output = preg_replace( '/\s*\/>/', '>', $output );
-
-               $this->assertEquals( $expected, $output, $description );
-       }
-
-       public static function provideBalancerTests() {
-               // Get the tests from html5lib-tests.json
-               $json = json_decode( file_get_contents(
-                       __DIR__ . '/html5lib-tests.json'
-               ), true );
-               // Munge this slightly into the format phpunit expects
-               // for providers, and filter out HTML constructs which
-               // the balancer doesn't support.
-               $tests = [];
-               $okre = "~ \A
-                       (?i:<!DOCTYPE\ html>)?
-                       <html><head></head><body>
-                       .*
-                       </body></html>
-               \z ~xs";
-               foreach ( $json as $filename => $cases ) {
-                       foreach ( $cases as $case ) {
-                               $html = $case['document']['html'];
-                               if ( !preg_match( $okre, $html ) ) {
-                                       // Skip tests which involve stuff in the <head> or
-                                       // weird doctypes.
-                                       continue;
-                               }
-                               // We used to do this:
-                               //   $html = substr( $html, strlen( $start ), -strlen( $end ) );
-                               // But now we use a different field in the test case,
-                               // which reports how domino would parse this case in a
-                               // no-quirks <body> context.  (The original test case may
-                               // have had a different context, or relied on quirks mode.)
-                               $html = $case['document']['noQuirksBodyHtml'];
-                               // Normalize case of SVG attributes.
-                               $html = str_replace( 'foreignObject', 'foreignobject', $html );
-                               // Normalize case of MathML attributes.
-                               $html = str_replace( 'definitionURL', 'definitionurl', $html );
-
-                               if (
-                                       isset( $case['document']['props']['comment'] ) &&
-                                       preg_match( ',<!--[^>]*<,', $html )
-                               ) {
-                                       // Skip tests which include HTML comments containing
-                                       // the < character, which we don't support.
-                                       continue;
-                               }
-                               if ( strpos( $case['data'], '<![CDATA[' ) !== false ) {
-                                       // Skip tests involving <![CDATA[ ]]> quoting.
-                                       continue;
-                               }
-                               if (
-                                       stripos( $case['data'], '<!DOCTYPE' ) !== false &&
-                                       stripos( $case['data'], '<!DOCTYPE html>' ) === false
-                               ) {
-                                       // Skip tests involving unusual doctypes.
-                                       continue;
-                               }
-                               $literalre = "~ <rdar: | < /? (
-                                       html | head | body | frame | frameset | plaintext
-                               ) > ~xi";
-                               if ( preg_match( $literalre, $case['data'] ) ) {
-                                       // Skip tests involving some literal tags, which are
-                                       // unsupported but don't show up in the expected output.
-                                       continue;
-                               }
-                               if (
-                                       isset( $case['document']['props']['tags']['iframe'] ) ||
-                                       isset( $case['document']['props']['tags']['noembed'] ) ||
-                                       isset( $case['document']['props']['tags']['noscript'] ) ||
-                                       isset( $case['document']['props']['tags']['script'] ) ||
-                                       isset( $case['document']['props']['tags']['svg script'] ) ||
-                                       isset( $case['document']['props']['tags']['svg title'] ) ||
-                                       isset( $case['document']['props']['tags']['title'] ) ||
-                                       isset( $case['document']['props']['tags']['xmp'] )
-                               ) {
-                                       // Skip tests with unsupported tags which *do* show
-                                       // up in the expected output.
-                                       continue;
-                               }
-                               if (
-                                       $filename === 'entities01.dat' ||
-                                       $filename === 'entities02.dat' ||
-                                       preg_match( '/&([a-z]+|#x[0-9A-F]+);/i', $case['data'] ) ||
-                                       preg_match( '/^(&|&#|&#X|&#x|&#45|&x-test|&AMP)$/', $case['data'] )
-                               ) {
-                                       // Skip tests involving entity encoding.
-                                       continue;
-                               }
-                               if (
-                                       isset( $case['document']['props']['tagWithLt'] ) ||
-                                       isset( $case['document']['props']['attrWithFunnyChar'] ) ||
-                                       preg_match( ':^(</b test|<di|<foo bar=qux/>)$:', $case['data'] ) ||
-                                       preg_match( ':</p<p>:', $case['data'] ) ||
-                                       preg_match( ':<b &=&amp>|<p/x/y/z>:', $case['data'] )
-                               ) {
-                                       // Skip tests with funny tag or attribute names,
-                                       // which are really tests of the HTML tokenizer, not
-                                       // the tree builder.
-                                       continue;
-                               }
-                               if (
-                                       preg_match( ':encoding=" text/html "|type=" hidden":', $case['data'] )
-                               ) {
-                                       // The Sanitizer normalizes whitespace in attribute
-                                       // values, which makes this test case invalid.
-                                       continue;
-                               }
-                               if ( $filename === 'plain-text-unsafe.dat' ) {
-                                       // Skip tests with ASCII null, etc.
-                                       continue;
-                               }
-                               $data = preg_replace(
-                                       '~<!DOCTYPE html>~i', '', $case['data']
-                               );
-                               $tests[] = [
-                                       $filename, # use better description?
-                                       $data,
-                                       $html,
-                                       false # strict HTML5 compat mode, no tidy
-                               ];
-                       }
-               }
-
-               # Some additional tests for mediawiki-specific features
-               $tests[] = [
-                       'Round-trip serialization for <pre>/<listing>/<textarea>',
-                       "<pre>\n\na</pre><listing>\n\nb</listing><textarea>\n\nc</textarea>",
-                       "<pre>\n\na</pre><listing>\n\nb</listing><textarea>\n\nc</textarea>",
-                       true # use the tidy-compatible mode
-               ];
-
-               return $tests;
-       }
-}
index 9e5163f..9c7c50f 100644 (file)
@@ -43,6 +43,10 @@ class ClassCollectorTest extends PHPUnit\Framework\TestCase {
                                "namespace Example;\nclass Foo {}\nclass_alias( Foo::class, 'Bar' );",
                                [ 'Example\Foo', 'Bar' ],
                        ],
+                       [
+                               "new class() extends Foo {}",
+                               []
+                       ]
                ];
        }
 
index 7c99614..5a554a0 100644 (file)
@@ -57,19 +57,59 @@ class LanguageCrhTest extends LanguageClassesTestCase {
                        ],
                        [ // recent problem words, part 1
                                [
-                                       'crh'      => 'künü куню sürgünligi сюргюнлиги özü озю etti этти',
-                                       'crh-cyrl' => 'куню куню сюргюнлиги сюргюнлиги озю озю этти этти',
-                                       'crh-latn' => 'künü künü sürgünligi sürgünligi özü özü etti etti',
+                                       'crh'      => 'künü куню sürgünligi сюргюнлиги özü озю etti этти esas эсас dört дёрт',
+                                       'crh-cyrl' => 'куню куню сюргюнлиги сюргюнлиги озю озю этти этти эсас эсас дёрт дёрт',
+                                       'crh-latn' => 'künü künü sürgünligi sürgünligi özü özü etti etti esas esas dört dört',
                                ],
-                               'künü куню sürgünligi сюргюнлиги özü озю etti этти'
+                               'künü куню sürgünligi сюргюнлиги özü озю etti этти esas эсас dört дёрт'
                        ],
                        [ // recent problem words, part 2
                                [
-                                       'crh'      => 'esas эсас dört дёрт keldi кельди',
-                                       'crh-cyrl' => 'эсас эсас дёрт дёрт кельди кельди',
-                                       'crh-latn' => 'esas esas dört dört keldi keldi',
+                                       'crh'      => 'keldi кельди km² км² yüz юзь AQŞ АКъШ ŞSCBnen ШСДжБнен iyül июль',
+                                       'crh-cyrl' => 'кельди кельди км² км² юзь юзь АКъШ АКъШ ШСДжБнен ШСДжБнен июль июль',
+                                       'crh-latn' => 'keldi keldi km² km² yüz yüz AQŞ AQŞ ŞSCBnen ŞSCBnen iyül iyül',
                                ],
-                               'esas эсас dört дёрт keldi кельди'
+                               'keldi кельди km² км² yüz юзь AQŞ АКъШ ŞSCBnen ШСДжБнен iyül июль'
+                       ],
+                       [ // recent problem words, part 3
+                               [
+                                       'crh'      => 'işğal ишгъаль işğalcilerine ишгъальджилерине rayon район üst усть',
+                                       'crh-cyrl' => 'ишгъаль ишгъаль ишгъальджилерине ишгъальджилерине район район усть усть',
+                                       'crh-latn' => 'işğal işğal işğalcilerine işğalcilerine rayon rayon üst üst',
+                               ],
+                               'işğal ишгъаль işğalcilerine ишгъальджилерине rayon район üst усть'
+                       ],
+                       [ // recent problem words, part 4
+                               [
+                                       'crh'      => 'rayonınıñ районынынъ Noğay Ногъай Yürtü Юрьтю vatandan ватандан',
+                                       'crh-cyrl' => 'районынынъ районынынъ Ногъай Ногъай Юрьтю Юрьтю ватандан ватандан',
+                                       'crh-latn' => 'rayonınıñ rayonınıñ Noğay Noğay Yürtü Yürtü vatandan vatandan',
+                               ],
+                               'rayonınıñ районынынъ Noğay Ногъай Yürtü Юрьтю vatandan ватандан'
+                       ],
+                       [ // recent problem words, part 5
+                               [
+                                       'crh'      => 'ком-кок köm-kök rol роль AQQI АКЪКЪЫ DAĞĞA ДАГЪГЪА 13-ünci 13-юнджи',
+                                       'crh-cyrl' => 'ком-кок ком-кок роль роль АКЪКЪЫ АКЪКЪЫ ДАГЪГЪА ДАГЪГЪА 13-юнджи 13-юнджи',
+                                       'crh-latn' => 'köm-kök köm-kök rol rol AQQI AQQI DAĞĞA DAĞĞA 13-ünci 13-ünci',
+                               ],
+                               'ком-кок köm-kök rol роль AQQI АКЪКЪЫ DAĞĞA ДАГЪГЪА 13-ünci 13-юнджи'
+                       ],
+                       [ // recent problem words, part 6
+                               [
+                                       'crh'      => 'ДЖУРЬМЕК CÜRMEK кетсин ketsin джумлеси cümlesi ильи ilyi Ильи İlyi',
+                                       'crh-cyrl' => 'ДЖУРЬМЕК ДЖУРЬМЕК кетсин кетсин джумлеси джумлеси ильи ильи Ильи Ильи',
+                                       'crh-latn' => 'CÜRMEK CÜRMEK ketsin ketsin cümlesi cümlesi ilyi ilyi İlyi İlyi',
+                               ],
+                               'ДЖУРЬМЕК CÜRMEK кетсин ketsin джумлеси cümlesi ильи ilyi Ильи İlyi'
+                       ],
+                       [ // regex pattern words
+                               [
+                                       'crh'      => 'köyünden коюнден ange аньге',
+                                       'crh-cyrl' => 'коюнден коюнден аньге аньге',
+                                       'crh-latn' => 'köyünden köyünden ange ange',
+                               ],
+                               'köyünden коюнден ange аньге'
                        ],
                        [ // multi part words
                                [
@@ -79,13 +119,61 @@ class LanguageCrhTest extends LanguageClassesTestCase {
                                ],
                                'эки юз eki yüz'
                        ],
-                       [ // ALL CAPS, made up acronyms (not 100% sure these are correct)
+                       [ // affix patterns
                                [
-                                       'crh'      => 'ÑAB QIC ĞUK COT НЪАБ КЪЫДж ГЪУК ДЖОТ CA ДЖА',
-                                       'crh-cyrl' => 'НЪАБ КЪЫДж ГЪУК ДЖОТ НЪАБ КЪЫДж ГЪУК ДЖОТ ДЖА ДЖА',
+                                       'crh'      => 'köyniñ койнинъ Avcıköyde Авджыкойде ekvatorial экваториаль Canköy Джанкой',
+                                       'crh-cyrl' => 'койнинъ койнинъ Авджыкойде Авджыкойде экваториаль экваториаль Джанкой Джанкой',
+                                       'crh-latn' => 'köyniñ köyniñ Avcıköyde Avcıköyde ekvatorial ekvatorial Canköy Canköy',
+                               ],
+                               'köyniñ койнинъ Avcıköyde Авджыкойде ekvatorial экваториаль Canköy Джанкой'
+                       ],
+                       [ // Roman numerals and quotes, esp. single-letter Roman numerals at the end of a string
+                               [
+                                       'crh'      => 'VI,VII IX “dört” «дёрт» XI XII I V X L C D M',
+                                       'crh-cyrl' => 'VI,VII IX «дёрт» «дёрт» XI XII I V X L C D M',
+                                       'crh-latn' => 'VI,VII IX “dört” "dört" XI XII I V X L C D M',
+                               ],
+                               'VI,VII IX “dört” «дёрт» XI XII I V X L C D M'
+                       ],
+                       [ // Roman numerals vs Initials, part 1 - Roman numeral initials without spaces
+                               [
+                                       'crh'      => 'A.B.C.D.M. Qadırova XII, А.Б.Дж.Д.М. Къадырова XII',
+                                       'crh-cyrl' => 'А.Б.Дж.Д.М. Къадырова XII, А.Б.Дж.Д.М. Къадырова XII',
+                                       'crh-latn' => 'A.B.C.D.M. Qadırova XII, A.B.C.D.M. Qadırova XII',
+                               ],
+                               'A.B.C.D.M. Qadırova XII, А.Б.Дж.Д.М. Къадырова XII'
+                       ],
+                       [ // Roman numerals vs Initials, part 2 - Roman numeral initials with spaces
+                               [
+                                       'crh'      => 'G. H. I. V. X. L. Memetov III, Г. Х. Ы. В. X. Л. Меметов III',
+                                       'crh-cyrl' => 'Г. Х. Ы. В. X. Л. Меметов III, Г. Х. Ы. В. X. Л. Меметов III',
+                                       'crh-latn' => 'G. H. I. V. X. L. Memetov III, G. H. I. V. X. L. Memetov III',
+                               ],
+                               'G. H. I. V. X. L. Memetov III, Г. Х. Ы. В. X. Л. Меметов III'
+                       ],
+                       [ // ALL CAPS, made up acronyms
+                               [
+                                       'crh'      => 'ÑAB QIC ĞUK COT НЪАБ КЪЫДЖ ГЪУК ДЖОТ CA ДЖА',
+                                       'crh-cyrl' => 'НЪАБ КЪЫДЖ ГЪУК ДЖОТ НЪАБ КЪЫДЖ ГЪУК ДЖОТ ДЖА ДЖА',
                                        'crh-latn' => 'ÑAB QIC ĞUK COT ÑAB QIC ĞUK COT CA CA',
                                ],
-                               'ÑAB QIC ĞUK COT НЪАБ КЪЫДж ГЪУК ДЖОТ CA ДЖА'
+                               'ÑAB QIC ĞUK COT НЪАБ КЪЫДЖ ГЪУК ДЖОТ CA ДЖА'
+                       ],
+                       [ // Many-to-one mappings: many Cyrillic to one Latin
+                               [
+                                       'crh'      => 'шофер шофёр şoför корбекул корьбекул корьбекуль körbekül',
+                                       'crh-cyrl' => 'шофер шофёр шофёр корбекул корьбекул корьбекуль корьбекуль',
+                                       'crh-latn' => 'şoför şoför şoför körbekül körbekül körbekül körbekül',
+                               ],
+                               'шофер шофёр şoför корбекул корьбекул корьбекуль körbekül'
+                       ],
+                       [ // Many-to-one mappings: many Latin to one Cyrillic
+                               [
+                                       'crh'      => 'fevqülade fevqulade февкъульаде beyude beyüde бейуде',
+                                       'crh-cyrl' => 'февкъульаде февкъульаде февкъульаде бейуде бейуде бейуде',
+                                       'crh-latn' => 'fevqülade fevqulade fevqulade beyude beyüde beyüde',
+                               ],
+                               'fevqülade fevqulade февкъульаде beyude beyüde бейуде'
                        ],
                ];
        }
index 417ad3d..7431b29 100644 (file)
                        } );
        } );
 
+       QUnit.test( 'getToken() - no query', function ( assert ) {
+               var api = new mw.Api(),
+                       // Same-origin warning and missing query in response.
+                       serverRsp = {
+                               warnings: {
+                                       tokens: {
+                                               '*': 'Tokens may not be obtained when the same-origin policy is not applied.'
+                                       }
+                               }
+                       };
+
+               this.server.respondWith( /type=testnoquery/, [ 200, { 'Content-Type': 'application/json' },
+                       JSON.stringify( serverRsp )
+               ] );
+
+               return api.getToken( 'testnoquery' )
+                       .then( function () { assert.fail( 'Expected response missing a query to be rejected' ); } )
+                       .catch( function ( err, rsp ) {
+                               assert.equal( err, 'query-missing', 'Expected no query error code' );
+                               assert.deepEqual( rsp, serverRsp );
+                       } );
+       } );
+
        QUnit.test( 'getToken() - deprecated', function ( assert ) {
                // Cache API endpoint from default to avoid cachehit in mw.user.tokens
                var api = new mw.Api( { ajax: { url: '/postWithToken/api.php' } } ),
index 0653dfd..71362fd 100644 (file)
        } );
 
        QUnit.test( 'Integration', function ( assert ) {
-               var expected, logSpy, msg;
+               var expected, msg;
 
                expected = '<b><a title="Bold" href="/wiki/Bold">Bold</a>!</b>';
                mw.messages.set( 'integration-test', '<b>[[Bold]]!</b>' );
 
-               this.suppressWarnings();
-               logSpy = this.sandbox.spy( mw.log, 'warn' );
-               assert.equal(
-                       window.gM( 'integration-test' ),
-                       expected,
-                       'Global function gM() works correctly'
-               );
-               assert.equal( logSpy.callCount, 1, 'mw.log.warn called' );
-               this.restoreWarnings();
-
                assert.equal(
                        mw.message( 'integration-test' ).parse(),
                        expected,
index b8464e9..f776d41 100644 (file)
                assert.equal( util.rawurlencode( 'Test:A & B/Here' ), 'Test%3AA%20%26%20B%2FHere' );
        } );
 
-       QUnit.test( 'escapeId', function ( assert ) {
-               mw.config.set( 'wgFragmentMode', [ 'legacy' ] );
-               $.each( {
-                       '+': '.2B',
-                       '&': '.26',
-                       '=': '.3D',
-                       ':': ':',
-                       ';': '.3B',
-                       '@': '.40',
-                       $: '.24',
-                       '-_.': '-_.',
-                       '!': '.21',
-                       '*': '.2A',
-                       '/': '.2F',
-                       '[]': '.5B.5D',
-                       '<>': '.3C.3E',
-                       '\'': '.27',
-                       '§': '.C2.A7',
-                       'Test:A & B/Here': 'Test:A_.26_B.2FHere',
-                       'A&B&amp;C&amp;amp;D&amp;amp;amp;E': 'A.26B.26amp.3BC.26amp.3Bamp.3BD.26amp.3Bamp.3Bamp.3BE'
-               }, function ( input, output ) {
-                       assert.equal( util.escapeId( input ), output );
-               } );
-       } );
-
        QUnit.test( 'escapeIdForAttribute', function ( assert ) {
                // Test cases are kept in sync with SanitizerTest.php
                var text = 'foo тест_#%!\'()[]:<>',
                        legacyEncoded = 'foo_.D1.82.D0.B5.D1.81.D1.82_.23.25.21.27.28.29.5B.5D:.3C.3E',
                        html5Encoded = 'foo_тест_#%!\'()[]:<>',
-                       html5Experimental = 'foo_тест_!_()[]:<>',
                        // Settings: this is $wgFragmentMode
                        legacy = [ 'legacy' ],
                        legacyNew = [ 'legacy', 'html5' ],
                        newLegacy = [ 'html5', 'legacy' ],
-                       allNew = [ 'html5' ],
-                       experimentalLegacy = [ 'html5-legacy', 'legacy' ],
-                       newExperimental = [ 'html5', 'html5-legacy' ];
+                       allNew = [ 'html5' ];
 
                // Test cases are kept in sync with SanitizerTest.php
                [
                        // New world: HTML5 links, legacy fallbacks
                        [ newLegacy, text, html5Encoded ],
                        // Distant future: no legacy fallbacks
-                       [ allNew, text, html5Encoded ],
-                       // Someone flipped $wgExperimentalHtmlIds on
-                       [ experimentalLegacy, text, html5Experimental ],
-                       // Migration from $wgExperimentalHtmlIds to modern HTML5
-                       [ newExperimental, text, html5Encoded ]
+                       [ allNew, text, html5Encoded ]
                ].forEach( function ( testCase ) {
                        mw.config.set( 'wgFragmentMode', testCase[ 0 ] );
 
                var text = 'foo тест_#%!\'()[]:<>',
                        legacyEncoded = 'foo_.D1.82.D0.B5.D1.81.D1.82_.23.25.21.27.28.29.5B.5D:.3C.3E',
                        html5Encoded = 'foo_тест_#%!\'()[]:<>',
-                       html5Experimental = 'foo_тест_!_()[]:<>',
                        // Settings: this is wgFragmentMode
                        legacy = [ 'legacy' ],
                        legacyNew = [ 'legacy', 'html5' ],
                        newLegacy = [ 'html5', 'legacy' ],
-                       allNew = [ 'html5' ],
-                       experimentalLegacy = [ 'html5-legacy', 'legacy' ],
-                       newExperimental = [ 'html5', 'html5-legacy' ];
+                       allNew = [ 'html5' ];
 
                [
                        // Pure legacy: how MW worked before 2017
                        // New world: HTML5 links, legacy fallbacks
                        [ newLegacy, text, html5Encoded ],
                        // Distant future: no legacy fallbacks
-                       [ allNew, text, html5Encoded ],
-                       // Someone flipped wgExperimentalHtmlIds on
-                       [ experimentalLegacy, text, html5Experimental ],
-                       // Migration from wgExperimentalHtmlIds to modern HTML5
-                       [ newExperimental, text, html5Encoded ]
+                       [ allNew, text, html5Encoded ]
                ].forEach( function ( testCase ) {
                        mw.config.set( 'wgFragmentMode', testCase[ 0 ] );
 
index 85fc310..e39226c 100644 (file)
@@ -9,6 +9,6 @@
                "browser": false
        },
        "rules":{
-               "no-console":0
+               "no-console": 0
        }
 }
index b15d407..274eb14 100644 (file)
@@ -5,9 +5,8 @@
 - [Chrome](https://www.google.com/chrome/)
 - [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/)
 - [Node.js](https://nodejs.org/en/)
-- [MediaWiki-Vagrant](https://www.mediawiki.org/wiki/MediaWiki-Vagrant)
 
-Set up MediaWiki-Vagrant:
+If using MediaWiki-Vagrant:
 
     cd mediawiki/vagrant
     vagrant up
@@ -24,7 +23,7 @@ Set up MediaWiki-Vagrant:
 By default, Chrome will run in headless mode. If you want to see Chrome, set DISPLAY
 environment variable to any value:
 
-    DISPLAY=:1 npm run selenium
+    DISPLAY=1 npm run selenium
 
 To run only one file (for example page.js), you first need to spawn the chromedriver:
 
index 105f409..a0b70a3 100644 (file)
@@ -1,5 +1,6 @@
-'use strict';
-const Page = require( './page' );
+const Page = require( './page' ),
+       // https://github.com/Fannon/mwbot
+       MWBot = require( 'mwbot' );
 
 class CreateAccountPage extends Page {
 
@@ -22,18 +23,14 @@ class CreateAccountPage extends Page {
        }
 
        apiCreateAccount( username, password ) {
-
-               const MWBot = require( 'mwbot' ), // https://github.com/Fannon/mwbot
-                       Promise = require( 'bluebird' );
                let bot = new MWBot();
 
-               return Promise.coroutine( function* () {
-                       yield bot.loginGetCreateaccountToken( {
-                               apiUrl: `${browser.options.baseUrl}/api.php`,
-                               username: browser.options.username,
-                               password: browser.options.password
-                       } );
-                       yield bot.request( {
+               return bot.loginGetCreateaccountToken( {
+                       apiUrl: `${browser.options.baseUrl}/api.php`,
+                       username: browser.options.username,
+                       password: browser.options.password
+               } ).then( function () {
+                       return bot.request( {
                                action: 'createaccount',
                                createreturnurl: browser.options.baseUrl,
                                createtoken: bot.createaccountToken,
@@ -41,9 +38,8 @@ class CreateAccountPage extends Page {
                                password: password,
                                retype: password
                        } );
-               } ).call( this );
-
+               } );
        }
-
 }
+
 module.exports = new CreateAccountPage();
index d43cb9f..ec03409 100644 (file)
@@ -1,8 +1,8 @@
-'use strict';
-const Page = require( './page' );
+const Page = require( './page' ),
+       // https://github.com/Fannon/mwbot
+       MWBot = require( 'mwbot' );
 
 class DeletePage extends Page {
-
        get reason() { return browser.element( '#wpReason' ); }
        get watch() { return browser.element( '#wpWatch' ); }
        get submit() { return browser.element( '#wpConfirmB' ); }
@@ -19,21 +19,16 @@ class DeletePage extends Page {
        }
 
        apiDelete( name, reason ) {
-
-               const MWBot = require( 'mwbot' ), // https://github.com/Fannon/mwbot
-                       Promise = require( 'bluebird' );
                let bot = new MWBot();
 
-               return Promise.coroutine( function* () {
-                       yield bot.loginGetEditToken( {
-                               apiUrl: `${browser.options.baseUrl}/api.php`,
-                               username: browser.options.username,
-                               password: browser.options.password
-                       } );
-                       yield bot.delete( name, reason );
-               } ).call( this );
-
+               return bot.loginGetEditToken( {
+                       apiUrl: `${browser.options.baseUrl}/api.php`,
+                       username: browser.options.username,
+                       password: browser.options.password
+               } ).then( function () {
+                       return bot.delete( name, reason );
+               } );
        }
-
 }
+
 module.exports = new DeletePage();
index 33a27f0..a1784f4 100644 (file)
@@ -1,8 +1,8 @@
-'use strict';
-const Page = require( './page' );
+const Page = require( './page' ),
+       // https://github.com/Fannon/mwbot
+       MWBot = require( 'mwbot' );
 
 class EditPage extends Page {
-
        get content() { return browser.element( '#wpTextbox1' ); }
        get displayedContent() { return browser.element( '#mw-content-text' ); }
        get heading() { return browser.element( '#firstHeading' ); }
@@ -19,21 +19,16 @@ class EditPage extends Page {
        }
 
        apiEdit( name, content ) {
-
-               const MWBot = require( 'mwbot' ), // https://github.com/Fannon/mwbot
-                       Promise = require( 'bluebird' );
                let bot = new MWBot();
 
-               return Promise.coroutine( function* () {
-                       yield bot.loginGetEditToken( {
-                               apiUrl: `${browser.options.baseUrl}/api.php`,
-                               username: browser.options.username,
-                               password: browser.options.password
-                       } );
-                       yield bot.edit( name, content, `Created page with "${content}"` );
-               } ).call( this );
-
+               return bot.loginGetEditToken( {
+                       apiUrl: `${browser.options.baseUrl}/api.php`,
+                       username: browser.options.username,
+                       password: browser.options.password
+               } ).then( function () {
+                       return bot.edit( name, content, `Created page with "${content}"` );
+               } );
        }
-
 }
+
 module.exports = new EditPage();
index 869484e..60d7fd4 100644 (file)
@@ -1,13 +1,11 @@
-'use strict';
 const Page = require( './page' );
 
 class HistoryPage extends Page {
-
        get comment() { return browser.element( '#pagehistory .comment' ); }
 
        open( name ) {
                super.open( name + '&action=history' );
        }
-
 }
+
 module.exports = new HistoryPage();
index 77bb1f4..0974086 100644 (file)
@@ -1,8 +1,11 @@
-// From http://webdriver.io/guide/testrunner/pageobjects.html
-'use strict';
+/**
+ * Based on http://webdriver.io/guide/testrunner/pageobjects.html
+ */
+
 class Page {
        open( path ) {
                browser.url( browser.options.baseUrl + '/index.php?title=' + path );
        }
 }
+
 module.exports = Page;
index 98b87fe..9456b61 100644 (file)
@@ -1,8 +1,6 @@
-'use strict';
 const Page = require( './page' );
 
 class PreferencesPage extends Page {
-
        get realName() { return browser.element( '#mw-input-wprealname' ); }
        get save() { return browser.element( '#prefcontrol' ); }
 
@@ -15,6 +13,6 @@ class PreferencesPage extends Page {
                this.realName.setValue( realName );
                this.save.click();
        }
-
 }
+
 module.exports = new PreferencesPage();
index 071f7f9..be5be8c 100644 (file)
@@ -1,4 +1,3 @@
-'use strict';
 const Page = require( './page' );
 
 class RestorePage extends Page {
@@ -16,6 +15,6 @@ class RestorePage extends Page {
                this.reason.setValue( reason );
                this.submit.click();
        }
-
 }
+
 module.exports = new RestorePage();
index 0061d0c..557fb6b 100644 (file)
@@ -1,8 +1,6 @@
-'use strict';
 const Page = require( './page' );
 
 class UserLoginPage extends Page {
-
        get username() { return browser.element( '#wpName1' ); }
        get password() { return browser.element( '#wpPassword1' ); }
        get loginButton() { return browser.element( '#wpLoginAttempt' ); }
@@ -22,6 +20,6 @@ class UserLoginPage extends Page {
        loginAdmin() {
                this.login( browser.options.username, browser.options.password );
        }
-
 }
+
 module.exports = new UserLoginPage();
index 6b71019..4a5c254 100755 (executable)
@@ -1,5 +1,8 @@
 #!/usr/bin/env bash
 set -euo pipefail
+# Check the command before running in background so
+# that it can actually fail and have a descriptive error
+hash chromedriver
 chromedriver --url-base=/wd/hub --port=4444 &
 # Make sure it is killed to prevent file descriptors leak
 function kill_chromedriver() {
index 376dce5..197a235 100644 (file)
@@ -1,4 +1,3 @@
-'use strict';
 const assert = require( 'assert' ),
        DeletePage = require( '../pageobjects/delete.page' ),
        RestorePage = require( '../pageobjects/restore.page' ),
@@ -7,7 +6,6 @@ const assert = require( 'assert' ),
        UserLoginPage = require( '../pageobjects/userlogin.page' );
 
 describe( 'Page', function () {
-
        var content,
                name;
 
@@ -28,14 +26,12 @@ describe( 'Page', function () {
        } );
 
        it( 'should be creatable', function () {
-
                // create
                EditPage.edit( name, content );
 
                // check
                assert.equal( EditPage.heading.getText(), name );
                assert.equal( EditPage.displayedContent.getText(), content );
-
        } );
 
        it( 'should be re-creatable', function () {
@@ -61,7 +57,6 @@ describe( 'Page', function () {
        } );
 
        it( 'should be editable', function () {
-
                // create
                browser.call( function () {
                        return EditPage.apiEdit( name, content );
@@ -73,11 +68,9 @@ describe( 'Page', function () {
                // check
                assert.equal( EditPage.heading.getText(), name );
                assert.equal( EditPage.displayedContent.getText(), content );
-
        } );
 
        it( 'should have history', function () {
-
                // create
                browser.call( function () {
                        return EditPage.apiEdit( name, content );
@@ -86,11 +79,9 @@ describe( 'Page', function () {
                // check
                HistoryPage.open( name );
                assert.equal( HistoryPage.comment.getText(), `(Created page with "${content}")` );
-
        } );
 
        it( 'should be deletable', function () {
-
                // login
                UserLoginPage.loginAdmin();
 
@@ -107,11 +98,9 @@ describe( 'Page', function () {
                        DeletePage.displayedContent.getText(),
                        '"' + name + '" has been deleted. See deletion log for a record of recent deletions.\nReturn to Main Page.'
                );
-
        } );
 
        it( 'should be restorable', function () {
-
                // login
                UserLoginPage.loginAdmin();
 
@@ -130,7 +119,5 @@ describe( 'Page', function () {
 
                // check
                assert.equal( RestorePage.displayedContent.getText(), name + ' has been restored\nConsult the deletion log for a record of recent deletions and restorations.' );
-
        } );
-
 } );
index 3f3872d..62aac05 100644 (file)
@@ -1,11 +1,9 @@
-'use strict';
 const assert = require( 'assert' ),
        CreateAccountPage = require( '../pageobjects/createaccount.page' ),
        PreferencesPage = require( '../pageobjects/preferences.page' ),
        UserLoginPage = require( '../pageobjects/userlogin.page' );
 
 describe( 'User', function () {
-
        var password,
                username;
 
@@ -22,17 +20,14 @@ describe( 'User', function () {
        } );
 
        it( 'should be able to create account', function () {
-
                // create
                CreateAccountPage.createAccount( username, password );
 
                // check
                assert.equal( CreateAccountPage.heading.getText(), `Welcome, ${username}!` );
-
        } );
 
        it( 'should be able to log in', function () {
-
                // create
                browser.call( function () {
                        return CreateAccountPage.apiCreateAccount( username, password );
@@ -43,11 +38,9 @@ describe( 'User', function () {
 
                // check
                assert.equal( UserLoginPage.userPage.getText(), username );
-
        } );
 
        it( 'should be able to change preferences', function () {
-
                var realName = Math.random().toString();
 
                // create
@@ -63,7 +56,5 @@ describe( 'User', function () {
 
                // check
                assert.equal( PreferencesPage.realName.getValue(), realName );
-
        } );
-
 } );
index 0930a0f..5399fa4 100644 (file)
@@ -1,21 +1,6 @@
-'use strict';
-
 const fs = require( 'fs' ),
-       path = require( 'path' );
-
-let logPath, password, username;
-
-// username and password will be used only if
-// MEDIAWIKI_USER or MEDIAWIKI_PASSWORD environment variables are not set
-if ( process.env.JENKINS_HOME ) {
-       logPath = '../log/';
-       password = 'testpass';
-       username = 'WikiAdmin';
-} else {
-       logPath = './log/';
-       password = 'vagrant';
-       username = 'Admin';
-}
+       path = require( 'path' ),
+       logPath = process.env.LOG_DIR || './log/';
 
 function relPath( foo ) {
        return path.resolve( __dirname, '../..', foo );
@@ -23,28 +8,22 @@ function relPath( foo ) {
 
 exports.config = {
        // ======
-       // Custom
+       // Custom WDIO config specific to MediaWiki
        // ======
-       // Define any custom variables.
-       // Example:
-       // username: 'Admin',
-       // Use if from tests with:
-       // browser.options.username
-       username: process.env.MEDIAWIKI_USER === undefined ?
-               username :
-               process.env.MEDIAWIKI_USER,
-       password: process.env.MEDIAWIKI_PASSWORD === undefined ?
-               password :
-               process.env.MEDIAWIKI_PASSWORD,
-       //
+       // Use in a test as `browser.options.<key>`.
+
+       // Configure wiki admin user/pass via env
+       // Defaults are for convenience with MediaWiki-Vagrant
+       username: process.env.MEDIAWIKI_USER || 'Admin',
+       password: process.env.MEDIAWIKI_PASSWORD || 'vagrant',
+
        // ======
        // Sauce Labs
        // ======
-       //
        services: [ 'sauce' ],
        user: process.env.SAUCE_USERNAME,
        key: process.env.SAUCE_ACCESS_KEY,
-       //
+
        // ==================
        // Specify Test Files
        // ==================
@@ -52,7 +31,6 @@ exports.config = {
        // from which `wdio` was called. Notice that, if you are calling `wdio` from an
        // NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
        // directory is where your package.json resides, so `wdio` will be called from there.
-       //
        specs: [
                relPath( './tests/selenium/specs/**/*.js' ),
                relPath( './extensions/*/tests/selenium/specs/**/*.js' ),
@@ -61,9 +39,9 @@ exports.config = {
        ],
        // Patterns to exclude.
        exclude: [
-               './extensions/CirrusSearch/tests/selenium/specs/**/*.js'
+               relPath( './extensions/CirrusSearch/tests/selenium/specs/**/*.js' )
        ],
-       //
+
        // ============
        // Capabilities
        // ============
@@ -71,16 +49,15 @@ exports.config = {
        // time. Depending on the number of capabilities, WebdriverIO launches several test
        // sessions. Within your capabilities you can overwrite the spec and exclude options in
        // order to group specific specs to a specific capability.
-       //
+
        // First, you can define how many instances should be started at the same time. Let's
        // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
        // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
        // files and you set maxInstances to 10, all spec files will get tested at the same time
        // and 30 processes will get spawned. The property handles how many capabilities
        // from the same test should run tests.
-       //
        maxInstances: 1,
-       //
+
        // If you have trouble getting all important capabilities together, check out the
        // Sauce Labs platform configurator - a great tool to configure your capabilities:
        // https://docs.saucelabs.com/reference/platforms-configurator
@@ -91,20 +68,20 @@ exports.config = {
                // grid with only 5 firefox instances available you can make sure that not more than
                // 5 instances get started at a time.
                maxInstances: 1,
-               //
                browserName: 'chrome',
                chromeOptions: {
-                       // Run headless when there is no DISPLAY
-                       // --headless: since Chrome 59 https://chromium.googlesource.com/chromium/src/+/59.0.3030.0/headless/README.md
+                       // If DISPLAY is set, assume running from developer machine and/or with Xvfb.
+                       // Otherwise, use --headless (added in Chrome 59)
+                       // https://chromium.googlesource.com/chromium/src/+/59.0.3030.0/headless/README.md
                        args: (
                                process.env.DISPLAY ? [] : [ '--headless' ]
                        ).concat(
-                               // Disable Chrome sandbox when running in Docker
+                               // Chrome sandbox does not work in Docker
                                fs.existsSync( '/.dockerenv' ) ? [ '--no-sandbox' ] : []
                        )
                }
        } ],
-       //
+
        // ===================
        // Test Configurations
        // ===================
@@ -114,47 +91,43 @@ exports.config = {
        // the wdio-sync package. If you still want to run your tests in an async way
        // e.g. using promises you can set the sync option to false.
        sync: true,
-       //
+
        // Level of logging verbosity: silent | verbose | command | data | result | error
        logLevel: 'error',
-       //
+
        // Enables colors for log output.
        coloredLogs: true,
-       //
+
        // Warns when a deprecated command is used
        deprecationWarnings: true,
-       //
+
        // If you only want to run your tests until a specific amount of tests have failed use
        // bail (default is 0 - don't bail, run all tests).
        bail: 0,
-       //
+
        // Saves a screenshot to a given path if a command fails.
        screenshotPath: logPath,
-       //
+
        // Set a base URL in order to shorten url command calls. If your `url` parameter starts
        // with `/`, the base url gets prepended, not including the path portion of your baseUrl.
        // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
        // gets prepended directly.
        baseUrl: (
-               process.env.MW_SERVER === undefined ?
-                       'http://127.0.0.1:8080' :
-                       process.env.MW_SERVER
+               process.env.MW_SERVER || 'http://127.0.0.1:8080'
        ) + (
-               process.env.MW_SCRIPT_PATH === undefined ?
-                       '/w' :
-                       process.env.MW_SCRIPT_PATH
+               process.env.MW_SCRIPT_PATH || '/w'
        ),
-       //
+
        // Default timeout for all waitFor* commands.
        waitforTimeout: 10000,
-       //
+
        // Default timeout in milliseconds for request
        // if Selenium Grid doesn't send response
        connectionRetryTimeout: 90000,
-       //
+
        // Default request retries count
        connectionRetryCount: 3,
-       //
+
        // Initialize the browser instance with a WebdriverIO plugin. The object should have the
        // plugin name as key and the desired plugin options as properties. Make sure you have
        // the plugin installed before running any tests. The following plugins are currently
@@ -185,7 +158,7 @@ exports.config = {
        // Make sure you have the wdio adapter package for the specific framework installed
        // before running any tests.
        framework: 'mocha',
-       //
+
        // Test reporter for stdout.
        // The only one supported by default is 'dot'
        // see also: http://webdriver.io/guide/testrunner/reporters.html
@@ -195,14 +168,14 @@ exports.config = {
                        outputDir: logPath
                }
        },
-       //
+
        // Options to be passed to Mocha.
        // See the full list at http://mochajs.org/
        mochaOpts: {
                ui: 'bdd',
-               timeout: 20000
+               timeout: 60000
        },
-       //
+
        // =====
        // Hooks
        // =====
@@ -210,65 +183,73 @@ exports.config = {
        // it and to build services around it. You can either apply a single function or an array of
        // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
        // resolved to continue.
+
        /**
-       * Gets executed once before all workers get launched.
-       * @param {Object} config wdio configuration object
-       * @param {Array.<Object>} capabilities list of capabilities details
-       */
+        * Gets executed once before all workers get launched.
+        * @param {Object} config wdio configuration object
+        * @param {Array.<Object>} capabilities list of capabilities details
+        */
        // onPrepare: function (config, capabilities) {
        // },
+
        /**
-       * Gets executed just before initialising the webdriver session and test framework. It allows you
-       * to manipulate configurations depending on the capability or spec.
-       * @param {Object} config wdio configuration object
-       * @param {Array.<Object>} capabilities list of capabilities details
-       * @param {Array.<String>} specs List of spec file paths that are to be run
-       */
+        * Gets executed just before initialising the webdriver session and test framework. It allows you
+        * to manipulate configurations depending on the capability or spec.
+        * @param {Object} config wdio configuration object
+        * @param {Array.<Object>} capabilities list of capabilities details
+        * @param {Array.<String>} specs List of spec file paths that are to be run
+        */
        // beforeSession: function (config, capabilities, specs) {
        // },
+
        /**
-       * Gets executed before test execution begins. At this point you can access to all global
-       * variables like `browser`. It is the perfect place to define custom commands.
-       * @param {Array.<Object>} capabilities list of capabilities details
-       * @param {Array.<String>} specs List of spec file paths that are to be run
-       */
+        * Gets executed before test execution begins. At this point you can access to all global
+        * variables like `browser`. It is the perfect place to define custom commands.
+        * @param {Array.<Object>} capabilities list of capabilities details
+        * @param {Array.<String>} specs List of spec file paths that are to be run
+        */
        // before: function (capabilities, specs) {
        // },
+
        /**
-       * Runs before a WebdriverIO command gets executed.
-       * @param {String} commandName hook command name
-       * @param {Array} args arguments that command would receive
-       */
+        * Runs before a WebdriverIO command gets executed.
+        * @param {String} commandName hook command name
+        * @param {Array} args arguments that command would receive
+        */
        // beforeCommand: function (commandName, args) {
        // },
+
        /**
-       * Hook that gets executed before the suite starts
-       * @param {Object} suite suite details
-       */
+        * Hook that gets executed before the suite starts
+        * @param {Object} suite suite details
+        */
        // beforeSuite: function (suite) {
        // },
+
        /**
-       * Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
-       * @param {Object} test test details
-       */
+        * Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
+        * @param {Object} test test details
+        */
        // beforeTest: function (test) {
        // },
+
        /**
-       * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
-       * beforeEach in Mocha)
-       */
+        * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
+        * beforeEach in Mocha)
+        */
        // beforeHook: function () {
        // },
+
        /**
-       * Hook that gets executed _after_ a hook within the suite ends (e.g. runs after calling
-       * afterEach in Mocha)
-       */
+        * Hook that gets executed _after_ a hook within the suite ends (e.g. runs after calling
+        * afterEach in Mocha)
+        */
        // afterHook: function () {
        // },
        /**
-       * Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) ends.
-       * @param {Object} test test details
-       */
+        * Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) ends.
+        * @param {Object} test test details
+        */
        // from https://github.com/webdriverio/webdriverio/issues/269#issuecomment-306342170
        afterTest: function ( test ) {
                var filename, filePath;
@@ -284,45 +265,49 @@ exports.config = {
                browser.saveScreenshot( filePath );
                console.log( '\n\tScreenshot location:', filePath, '\n' );
        }
-       //
+
        /**
-       * Hook that gets executed after the suite has ended
-       * @param {Object} suite suite details
-       */
+        * Hook that gets executed after the suite has ended
+        * @param {Object} suite suite details
+        */
        // afterSuite: function (suite) {
        // },
+
        /**
-       * Runs after a WebdriverIO command gets executed
-       * @param {String} commandName hook command name
-       * @param {Array} args arguments that command would receive
-       * @param {Number} result 0 - command success, 1 - command error
-       * @param {Object} error error object if any
-       */
+        * Runs after a WebdriverIO command gets executed
+        * @param {String} commandName hook command name
+        * @param {Array} args arguments that command would receive
+        * @param {Number} result 0 - command success, 1 - command error
+        * @param {Object} error error object if any
+        */
        // afterCommand: function (commandName, args, result, error) {
        // },
+
        /**
-       * Gets executed after all tests are done. You still have access to all global variables from
-       * the test.
-       * @param {Number} result 0 - test pass, 1 - test fail
-       * @param {Array.<Object>} capabilities list of capabilities details
-       * @param {Array.<String>} specs List of spec file paths that ran
-       */
+        * Gets executed after all tests are done. You still have access to all global variables from
+        * the test.
+        * @param {Number} result 0 - test pass, 1 - test fail
+        * @param {Array.<Object>} capabilities list of capabilities details
+        * @param {Array.<String>} specs List of spec file paths that ran
+        */
        // after: function (result, capabilities, specs) {
        // },
+
        /**
-       * Gets executed right after terminating the webdriver session.
-       * @param {Object} config wdio configuration object
-       * @param {Array.<Object>} capabilities list of capabilities details
-       * @param {Array.<String>} specs List of spec file paths that ran
-       */
+        * Gets executed right after terminating the webdriver session.
+        * @param {Object} config wdio configuration object
+        * @param {Array.<Object>} capabilities list of capabilities details
+        * @param {Array.<String>} specs List of spec file paths that ran
+        */
        // afterSession: function (config, capabilities, specs) {
        // },
+
        /**
-       * Gets executed after all workers got shut down and the process is about to exit.
-       * @param {Object} exitCode 0 - success, 1 - fail
-       * @param {Object} config wdio configuration object
-       * @param {Array.<Object>} capabilities list of capabilities details
-       */
+        * Gets executed after all workers got shut down and the process is about to exit.
+        * @param {Object} exitCode 0 - success, 1 - fail
+        * @param {Object} config wdio configuration object
+        * @param {Array.<Object>} capabilities list of capabilities details
+        */
        // onComplete: function(exitCode, config, capabilities) {
        // }
 };