Merge "API: HTMLize and internationalize the help, add Special:ApiHelp"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 13 Oct 2014 22:19:47 +0000 (22:19 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 13 Oct 2014 22:19:47 +0000 (22:19 +0000)
1  2 
RELEASE-NOTES-1.25
docs/hooks.txt
includes/AutoLoader.php
includes/DefaultSettings.php
includes/api/ApiTokens.php
includes/specialpage/SpecialPageFactory.php
languages/i18n/en.json
languages/i18n/qqq.json
resources/Resources.php

diff --combined RELEASE-NOTES-1.25
@@@ -9,30 -9,60 +9,74 @@@ MediaWiki 1.25 is an alpha-quality bran
  production.
  
  === Configuration changes in 1.25 ===
 +* $wgPageShowWatchingUsers was removed.
 +* $wgLocalVirtualHosts has been added to replace $wgConf->localVHosts.
 +* $wgAntiLockFlags was removed.
  
  === New features in 1.25 ===
 +* (bug 58139) ResourceLoaderFileModule now supports language fallback
 +  for 'languageScripts'.
 +* Added a new hook, "ContentAlterParserOutput", to allow extensions to modify the
 +  parser output for a content object before links update.
 +* (bug 67341) SVG images will no longer be base64-encoded when being embedded
 +  in CSS. This results in slight size increase before gzip compression (due to
 +  percent-encoding), but up to 20% decrease after it.
  
  === Bug fixes in 1.25 ===
 +* (bug 71003) No additional code will be generated to try to load CSS-embedded
 +  SVG images in Internet Explorer 6 and 7, as they don't support them anyway.
 +* (bug 67021) On Special:BookSources, corrected validation of ISBNs (both
 +  10- and 13-digit forms) containing "X".
  
  === Action API changes in 1.25 ===
  * (bug 65403) XML tag highlighting is now only performed for formats
    "xmlfm" and "wddxfm".
+ * action=paraminfo supports generalized submodules (modules=query+value),
+   querymodules and formatmodules are deprecated
+ * action=paraminfo no longer outputs descriptions and other help text by
+   default. If needed, it may be requested using the new 'helpformat' parameter.
+ * action=help has been completely rewritten, and outputs help in HTML
+   rather than plain text.
+ * Hitting api.php without specifying an action now displays only the help for
+   the main module, with links to submodule help.
+ * API help is no longer displayed on errors.
+ * Internationalized messages returned by the API will be in the wiki's content
+   language by default. 'uselang' is now a recognized API parameter;
+   "uselang=user" may be used to select the language from the current user's
+   preferences.
  
  === Action API internal changes in 1.25 ===
+ * ApiHelp has been rewritten to support i18n and paginated HTML output.
+   Most existing modules should continue working without changes, but should do
+   the following:
+   * Add an i18n message "apihelp-{$moduleName}-description" to replace getDescription().
+   * Add i18n messages "apihelp-{$moduleName}-param-{$param}" for each parameter
+     to replace getParamDescription(). If necessary, the settings array returned
+     by getParams() can use the new ApiBase::PARAM_HELP_MSG key to override the
+     message.
+   * Implement getExamplesMessages() to replace getExamples().
+ * Modules with submodules (like action=query) must have their submodules
+   override ApiBase::getParent() to return the correct parent object.
+ * The 'APIGetDescription' and 'APIGetParamDescription' hooks are deprecated,
+   and will have no effect for modules using i18n messages. Use
+   'APIGetDescriptionMessages' and 'APIGetParamDescriptionMessages' instead.
+ * Api formatters will no longer be asked to display the help screen on errors.
+ * ApiMain::getCredits() was removed. The credits are available in the
+   'api-credits' i18n message.
+ * The following methods have been deprecated and may be removed in a future
+   release:
+   * ApiBase::getDescription
+   * ApiBase::getParamDescription
+   * ApiBase::getExamples
+   * ApiBase::makeHelpMsg
+   * ApiBase::makeHelpArrayToString
+   * ApiBase::makeHelpMsgParameters
+   * ApiFormatBase::setUnescapeAmps
+   * ApiFormatBase::getWantsHelp
+   * ApiFormatBase::setHelp
+   * ApiMain::setHelp
+   * ApiMain::reallyMakeHelpMsg
+   * ApiMain::makeHelpMsgHeader
  
  === Languages updated in 1.25 ===
  
@@@ -45,22 -75,8 +89,22 @@@ changes to languages because of Bugzill
    removed. See https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery for
    migration guide for creators and users of custom skins that relied on it.
  * Javascript variable 'wgFileCanRotate' now only available on Special:Upload.
 -* (bug 56257) Set site logo url in ResourceLoaderSiteModule instead of inline
 -  styles in the HTML output.
 +* (bug 56257) Set site logo from mediawiki.skinning.interface module instead of
 +  inline styles in the HTML.
 +* Removed ApiQueryUsers::getAutoGroups(). (deprecated since 1.20)
 +* Removed XmlDumpWriter::schemaVersion(). (deprecated since 1.20)
 +* Removed LogEventsList::getDisplayTitle(). (deprecated since 1.20)
 +* Removed Preferences::trySetUserEmail(). (deprecated since 1.20)
 +* Removed mw.user.name() and mw.user.anonymous() methods. (deprecated since 1.20)
 +* Removed 'ok' and 'err' parameters in the mediawiki.api modules. (deprecated
 +  since 1.20)
 +* Removed 'async' parameter from the  mw.Api#getCategories() method. (deprecated
 +  since 1.20)
 +* Removed 'jquery.json' module. (deprecated since 1.24)
 +  Use the 'json' module and global JSON object instead.
 +* Deprecated OutputPage::readOnlyPage() and OutputPage::rateLimited().
 +  Also, the former will now throw an MWException if called with one or more
 +  arguments.
  
  == Compatibility ==
  
diff --combined docs/hooks.txt
@@@ -384,15 -384,29 +384,29 @@@ $text : the new text of the article (ha
  &$params: Array of parameters
  $flags: int zero or OR-ed flags like ApiBase::GET_VALUES_FOR_HELP
  
- 'APIGetDescription': Use this hook to modify a module's description.
+ 'APIGetDescription': DEPRECATED! Use APIGetDescriptionMessages instead.
+ Use this hook to modify a module's description.
  &$module: ApiBase Module object
- &$desc: Array of descriptions
+ &$desc: String description, or array of description strings
  
- 'APIGetParamDescription': Use this hook to modify a module's parameter
- descriptions.
+ 'APIGetDescriptionMessages': Use this hook to modify a module's help message.
+ $module: ApiBase Module object
+ &$msg: Array of Message objects
+ 'APIGetParamDescription': DEPRECATED! Use APIGetParamDescriptionMessages instead.
+ Use this hook to modify a module's parameter descriptions.
  &$module: ApiBase Module object
  &$desc: Array of parameter descriptions
  
+ 'APIGetParamDescriptionMessages': Use this hook to modify a module's parameter descriptions.
+ $module: ApiBase Module object
+ &$msg: Array of arrays of Message objects
+ 'APIHelpModifyOutput': Use this hook to modify an API module's help output.
+ $module: ApiBase Module object
+ &$help: Array of HTML strings to be joined for the output.
+ $options: Array Options passed to ApiHelp::getHelp
  'APIQueryAfterExecute': After calling the execute() method of an
  action=query submodule. Use this to extend core API modules.
  &$module: Module object
@@@ -912,15 -926,6 +926,15 @@@ generation of HTML may be skipped, but 
  ParserOutput object.
  &$output: ParserOutput, to manipulate or replace
  
 +'ContentAlterParserOutput': Modify parser output for a given content object.
 +Called by Content::getParserOutput after parsing has finished. Can be used
 +for changes that depend on the result of the parsing but have to be done
 +before LinksUpdate is called (such as adding tracking categories based on
 +the rendered HTML).
 +$content: The Content to render
 +$title: Title of the page, as context
 +$parserOutput: ParserOutput to manipulate
 +
  'ConvertContent': Called by AbstractContent::convert when a conversion to another
  content model is requested.
  $content: The Content object to be converted.
diff --combined includes/AutoLoader.php
@@@ -164,6 -164,7 +164,6 @@@ $wgAutoloadLocalClasses = array
        'StubObject' => 'includes/StubObject.php',
        'StubUserLang' => 'includes/StubObject.php',
        'MWTimestamp' => 'includes/MWTimestamp.php',
 -      'TimestampException' => 'includes/TimestampException.php',
        'Title' => 'includes/Title.php',
        'TitleArray' => 'includes/TitleArray.php',
        'TitleArrayFromResult' => 'includes/TitleArrayFromResult.php',
        'UserBlockedError' => 'includes/exception/UserBlockedError.php',
        'UserNotLoggedIn' => 'includes/exception/UserNotLoggedIn.php',
        'ThrottledError' => 'includes/exception/ThrottledError.php',
 +      'TimestampException' => 'includes/exception/TimestampException.php',
        'ReadOnlyError' => 'includes/exception/ReadOnlyError.php',
        'PermissionsError' => 'includes/exception/PermissionsError.php',
        'MWException' => 'includes/exception/MWException.php',
        'UploadFromUrlJob' => 'includes/jobqueue/jobs/UploadFromUrlJob.php',
        'AssembleUploadChunksJob' => 'includes/jobqueue/jobs/AssembleUploadChunksJob.php',
        'PublishStashedFileJob' => 'includes/jobqueue/jobs/PublishStashedFileJob.php',
 +      'ThumbnailRenderJob' => 'includes/jobqueue/jobs/ThumbnailRenderJob.php',
  
        # includes/jobqueue/utils
        'BacklinkJobUtils' => 'includes/jobqueue/utils/BacklinkJobUtils.php',
        'ResourceLoaderModule' => 'includes/resourceloader/ResourceLoaderModule.php',
        'ResourceLoaderNoscriptModule' => 'includes/resourceloader/ResourceLoaderNoscriptModule.php',
        'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php',
 +      'ResourceLoaderSkinModule' => 'includes/resourceloader/ResourceLoaderSkinModule.php',
        'ResourceLoaderStartUpModule' => 'includes/resourceloader/ResourceLoaderStartUpModule.php',
        'ResourceLoaderUserCSSPrefsModule' =>
                'includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php',
        'SpecialAllMessages' => 'includes/specials/SpecialAllMessages.php',
        'SpecialAllMyUploads' => 'includes/specials/SpecialMyRedirectPages.php',
        'SpecialAllPages' => 'includes/specials/SpecialAllPages.php',
+       'SpecialApiHelp' => 'includes/specials/SpecialApiHelp.php',
        'SpecialBlankpage' => 'includes/specials/SpecialBlankpage.php',
        'SpecialBlock' => 'includes/specials/SpecialBlock.php',
        'SpecialBlockList' => 'includes/specials/SpecialBlockList.php',
@@@ -1242,46 -1242,6 +1242,46 @@@ $wgThumbnailBuckets = null
   */
  $wgThumbnailMinimumBucketDistance = 50;
  
 +/**
 + * When defined, is an array of thumbnail widths to be rendered at upload time. The idea is to
 + * prerender common thumbnail sizes, in order to avoid the necessity to render them on demand, which
 + * has a performance impact for the first client to view a certain size.
 + *
 + * This obviously means that more disk space is needed per upload upfront.
 + *
 + * @since 1.25
 + */
 +
 +$wgUploadThumbnailRenderMap = array();
 +
 +/**
 + * The method through which the thumbnails will be prerendered for the entries in
 + * $wgUploadThumbnailRenderMap
 + *
 + * The method can be either "http" or "jobqueue". The former uses an http request to hit the
 + * thumbnail's URL.
 + * This method only works if thumbnails are configured to be rendered by a 404 handler. The latter
 + * option uses the job queue to render the thumbnail.
 + *
 + * @since 1.25
 + */
 +$wgUploadThumbnailRenderMethod = 'jobqueue';
 +
 +/**
 + * When using the "http" wgUploadThumbnailRenderMethod, lets one specify a custom Host HTTP header.
 + *
 + * @since 1.25
 + */
 +$wgUploadThumbnailRenderHttpCustomHost = false;
 +
 +/**
 + * When using the "http" wgUploadThumbnailRenderMethod, lets one specify a custom domain to send the
 + * HTTP request to.
 + *
 + * @since 1.25
 + */
 +$wgUploadThumbnailRenderHttpCustomDomain = false;
 +
  /**
   * Default parameters for the "<gallery>" tag
   */
@@@ -1997,6 -1957,15 +1997,6 @@@ $wgAllowSlowParserFunctions = false
   */
  $wgAllowSchemaUpdates = true;
  
 -/**
 - * Anti-lock flags - bitfield
 - *   - ALF_NO_LINK_LOCK:
 - *       Don't use locking reads when updating the link table. This is
 - *       necessary for wikis with a high edit rate for performance
 - *       reasons, but may cause link table inconsistency
 - */
 -$wgAntiLockFlags = 0;
 -
  /**
   * Maximum article size in kilobytes
   */
@@@ -5617,6 -5586,11 +5617,6 @@@ $wgPreviewOnOpenNamespaces = array
        NS_CATEGORY => true
  );
  
 -/**
 - * Go button goes straight to the edit screen if the article doesn't exist.
 - */
 -$wgGoToEdit = false;
 -
  /**
   * Enable the UniversalEditButton for browsers that support it
   * (currently only Firefox with an extension)
@@@ -5717,9 -5691,9 +5717,9 @@@ $wgGitRepositoryViewers = array
  /**
   * Recentchanges items are periodically purged; entries older than this many
   * seconds will go.
 - * Default: 13 weeks = about three months
 + * Default: 90 days = about three months
   */
 -$wgRCMaxAge = 13 * 7 * 24 * 3600;
 +$wgRCMaxAge = 90 * 24 * 3600;
  
  /**
   * Filter $wgRCLinkDays by $wgRCMaxAge to avoid showing links for numbers
@@@ -5871,6 -5845,11 +5871,6 @@@ $wgAdvertisedFeedTypes = array( 'atom' 
   */
  $wgRCShowWatchingUsers = false; # UPO
  
 -/**
 - * Show watching users in Page views
 - */
 -$wgPageShowWatchingUsers = false;
 -
  /**
   * Show the amount of changed characters in recent changes
   */
@@@ -6167,6 -6146,7 +6167,7 @@@ $wgExtensionMessagesFiles = array()
   */
  $wgMessagesDirs = array(
        'core' => "$IP/languages/i18n",
+       'api' => "$IP/includes/api/i18n",
        'oojs-ui' => "$IP/resources/lib/oojs-ui/i18n",
  );
  
@@@ -6338,7 -6318,6 +6339,7 @@@ $wgJobClasses = array
        'uploadFromUrl' => 'UploadFromUrlJob',
        'AssembleUploadChunks' => 'AssembleUploadChunksJob',
        'PublishStashedFile' => 'PublishStashedFileJob',
 +      'ThumbnailRender' => 'ThumbnailRenderJob',
        'null' => 'NullJob'
  );
  
@@@ -7074,18 -7053,6 +7075,18 @@@ $wgAsyncHTTPTimeout = 25
   */
  $wgHTTPProxy = false;
  
 +/**
 + * Local virtual hosts.
 + *
 + * This lists domains that are configured as virtual hosts on the same machine.
 + * If a request is to be made to a domain listed here, or any subdomain thereof,
 + * then no proxy will be used.
 + * Command-line scripts are not affected by this setting and will always use
 + * proxy if it is configured.
 + * @since 1.25
 + */
 +$wgLocalVirtualHosts = array();
 +
  /**
   * Timeout for connections done internally (in seconds)
   * Only works for curl
@@@ -34,7 -34,6 +34,7 @@@ class ApiTokens extends ApiBase 
                $this->setWarning(
                        "action=tokens has been deprecated. Please use action=query&meta=tokens instead."
                );
 +              $this->logFeatureUsage( "action=tokens" );
  
                $params = $this->extractRequestParams();
                $res = array();
                return $types;
        }
  
+       public function isDeprecated() {
+               return true;
+       }
        public function getAllowedParams() {
                return array(
                        'type' => array(
@@@ -47,7 -47,7 +47,7 @@@ class SpecialPageFactory 
        /**
         * List of special page names to the subclass of SpecialPage which handles them.
         */
 -      private static $list = array(
 +      private static $coreList = array(
                // Maintenance Reports
                'BrokenRedirects' => 'BrokenRedirectsPage',
                'Deadendpages' => 'DeadendPagesPage',
@@@ -59,7 -59,7 +59,7 @@@
                'Withoutinterwiki' => 'WithoutInterwikiPage',
                'Protectedpages' => 'SpecialProtectedpages',
                'Protectedtitles' => 'SpecialProtectedtitles',
 -              'Shortpages' => 'ShortpagesPage',
 +              'Shortpages' => 'ShortPagesPage',
                'Uncategorizedcategories' => 'UncategorizedCategoriesPage',
                'Uncategorizedimages' => 'UncategorizedImagesPage',
                'Uncategorizedpages' => 'UncategorizedPagesPage',
@@@ -74,7 -74,7 +74,7 @@@
                'Wantedtemplates' => 'WantedTemplatesPage',
  
                // List of pages
 -              'Allpages' => 'SpecialAllpages',
 +              'Allpages' => 'SpecialAllPages',
                'Prefixindex' => 'SpecialPrefixindex',
                'Categories' => 'SpecialCategories',
                'Listredirects' => 'ListredirectsPage',
  
                // Data and tools
                'Statistics' => 'SpecialStatistics',
 -              'Allmessages' => 'SpecialAllmessages',
 +              'Allmessages' => 'SpecialAllMessages',
                'Version' => 'SpecialVersion',
                'Lockdb' => 'SpecialLockdb',
                'Unlockdb' => 'SpecialUnlockdb',
                'Booksources' => 'SpecialBookSources',
  
                // Unlisted / redirects
+               'ApiHelp' => 'SpecialApiHelp',
                'Blankpage' => 'SpecialBlankpage',
                'Diff' => 'SpecialDiff',
                'Emailuser' => 'SpecialEmailUser',
                'Userlogout' => 'SpecialUserlogout',
        );
  
 +      private static $list;
        private static $aliases;
  
        /**
                global $wgEnableEmail, $wgEnableJavaScriptTest;
                global $wgPageLanguageUseDB;
  
 -              if ( !is_object( self::$list ) ) {
 +              if ( !is_array( self::$list ) ) {
                        wfProfileIn( __METHOD__ );
  
 +                      self::$list = self::$coreList;
 +
                        if ( !$wgDisableCounters ) {
                                self::$list['Popularpages'] = 'PopularPagesPage';
                        }
  
        /**
         * Initialise and return the list of special page aliases.  Returns an object with
 -       * properties which can be accessed $obj->pagename - each property is an array of
 -       * aliases; the first in the array is the canonical alias.  All registered special
 -       * pages are guaranteed to have a property entry, and for that property array to
 -       * contain at least one entry (English fallbacks will be added if necessary).
 +       * properties which can be accessed $obj->pagename - each property name is an
 +       * alias, with the value being the canonical name of the special page. All
 +       * registered special pages are guaranteed to map to themselves.
         * @return object
         */
        private static function getAliasListObject() {
                if ( !is_object( self::$aliases ) ) {
                        global $wgContLang;
                        $aliases = $wgContLang->getSpecialPageAliases();
 -
 -                      $missingPages = self::getPageList();
 +                      $pageList = self::getPageList();
  
                        self::$aliases = array();
 +                      $keepAlias = array();
 +
 +                      // Force every canonical name to be an alias for itself.
 +                      foreach ( $pageList as $name => $stuff ) {
 +                              $caseFoldedAlias = $wgContLang->caseFold( $name );
 +                              self::$aliases[$caseFoldedAlias] = $name;
 +                              $keepAlias[$caseFoldedAlias] = 'canonical';
 +                      }
 +
                        // Check for $aliases being an array since Language::getSpecialPageAliases can return null
                        if ( is_array( $aliases ) ) {
                                foreach ( $aliases as $realName => $aliasList ) {
 -                                      foreach ( $aliasList as $alias ) {
 -                                              self::$aliases[$wgContLang->caseFold( $alias )] = $realName;
 +                                      $aliasList = array_values( $aliasList );
 +                                      foreach ( $aliasList as $i => $alias ) {
 +                                              $caseFoldedAlias = $wgContLang->caseFold( $alias );
 +
 +                                              if ( isset( self::$aliases[$caseFoldedAlias] ) &&
 +                                                      $realName === self::$aliases[$caseFoldedAlias]
 +                                              ) {
 +                                                      // Ignore same-realName conflicts
 +                                                      continue;
 +                                              }
 +
 +                                              if ( !isset( $keepAlias[$caseFoldedAlias] ) ) {
 +                                                      self::$aliases[$caseFoldedAlias] = $realName;
 +                                                      if ( !$i ) {
 +                                                              $keepAlias[$caseFoldedAlias] = 'first';
 +                                                      }
 +                                              } elseif ( !$i ) {
 +                                                      wfWarn( "First alias '$alias' for $realName conflicts with " .
 +                                                              "{$keepAlias[$caseFoldedAlias]} alias for " .
 +                                                              self::$aliases[$caseFoldedAlias]
 +                                                      );
 +                                              }
                                        }
 -                                      unset( $missingPages->$realName );
                                }
                        }
 -                      foreach ( $missingPages as $name => $stuff ) {
 -                              self::$aliases[$wgContLang->caseFold( $name )] = $name;
 -                      }
  
                        // Cast to object: func()[$key] doesn't work, but func()->$key does
                        self::$aliases = (object)self::$aliases;
                if ( isset( $specialPageList[$realName] ) ) {
                        $rec = $specialPageList[$realName];
  
 -                      if ( is_string( $rec ) ) {
 -                              $className = $rec;
 -                              $page = new $className;
 -                      } elseif ( is_callable( $rec ) ) {
 +                      if ( is_callable( $rec ) ) {
                                // Use callback to instantiate the special page
                                $page = call_user_func( $rec );
 +                      } elseif ( is_string( $rec ) ) {
 +                              $className = $rec;
 +                              $page = new $className;
                        } elseif ( is_array( $rec ) ) {
                                $className = array_shift( $rec );
                                // @deprecated, officially since 1.18, unofficially since forever
        public static function getLocalNameFor( $name, $subpage = false ) {
                global $wgContLang;
                $aliases = $wgContLang->getSpecialPageAliases();
 +              $aliasList = self::getAliasListObject();
  
 -              if ( isset( $aliases[$name][0] ) ) {
 -                      $name = $aliases[$name][0];
 -              } else {
 -                      // Try harder in case someone misspelled the correct casing
 +              // Find the first alias that maps back to $name
 +              if ( isset( $aliases[$name] ) ) {
                        $found = false;
 -                      // Check for $aliases being an array since Language::getSpecialPageAliases can return null
 +                      foreach ( $aliases[$name] as $alias ) {
 +                              $caseFoldedAlias = $wgContLang->caseFold( $alias );
 +                              $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
 +                              if ( isset( $aliasList->$caseFoldedAlias ) &&
 +                                      $aliasList->$caseFoldedAlias === $name
 +                              ) {
 +                                      $name = $alias;
 +                                      $found = true;
 +                                      break;
 +                              }
 +                      }
 +                      if ( !$found ) {
 +                              wfWarn( "Did not find a usable alias for special page '$name'. " .
 +                                      "It seems all defined aliases conflict?" );
 +                      }
 +              } else {
 +                      // Check if someone misspelled the correct casing
                        if ( is_array( $aliases ) ) {
                                foreach ( $aliases as $n => $values ) {
                                        if ( strcasecmp( $name, $n ) === 0 ) {
                                                wfWarn( "Found alias defined for $n when searching for " .
                                                        "special page aliases for $name. Case mismatch?" );
 -                                              $name = $values[0];
 -                                              $found = true;
 -                                              break;
 +                                              return self::getLocalNameFor( $n, $subpage );
                                        }
                                }
                        }
 -                      if ( !$found ) {
 -                              wfWarn( "Did not find alias for special page '$name'. " .
 -                                      "Perhaps no aliases are defined for it?" );
 -                      }
 +
 +                      wfWarn( "Did not find alias for special page '$name'. " .
 +                              "Perhaps no aliases are defined for it?" );
                }
 +
                if ( $subpage !== false && !is_null( $subpage ) ) {
                        $name = "$name/$subpage";
                }
diff --combined languages/i18n/en.json
        "blocked-mailpassword": "Your IP address is blocked from editing, and so is not allowed to use the password recovery function to prevent abuse.",
        "eauthentsent": "A confirmation email has been sent to the specified email address.\nBefore any other email is sent to the account, you will have to follow the instructions in the email, to confirm that the account is actually yours.",
        "throttled-mailpassword": "A password reset email has already been sent, within the last {{PLURAL:$1|hour|$1 hours}}.\nTo prevent abuse, only one password reset email will be sent per {{PLURAL:$1|hour|$1 hours}}.",
 -      "loginstart": "",
 -      "loginend": "",
 -      "loginend-https": "",
 -      "signupstart": "{{int:loginstart}}",
 -      "signupend": "{{int:loginend}}",
 +      "signupstart": "",
 +      "signupend": "",
        "signupend-https": "",
        "mailerror": "Error sending mail: $1",
        "acct_creation_throttle_hit": "Visitors to this wiki using your IP address have created {{PLURAL:$1|1 account|$1 accounts}} in the last day, which is the maximum allowed in this time period.\nAs a result, visitors using this IP address cannot create any more accounts at the moment.",
        "pager-older-n": "{{PLURAL:$1|older 1|older $1}}",
        "suppress": "Oversight",
        "querypage-disabled": "This special page is disabled for performance reasons.",
+       "apihelp": "API help",
+       "apihelp-summary": "",
+       "apihelp-no-such-module": "Module \"$1\" not found.",
+       "apihelp-link": "[[Special:ApiHelp/$1|$2]]",
        "booksources": "Book sources",
        "booksources-summary": "",
        "booksources-search-legend": "Search for book sources",
        "wlheader-enotif": "Email notification is enabled.",
        "wlheader-showupdated": "Pages that have been changed since you last visited them are shown in <strong>bold</strong>.",
        "wlnote": "Below {{PLURAL:$1|is the last change|are the last <strong>$1</strong> changes}} in the last {{PLURAL:$2|hour|<strong>$2</strong> hours}}, as of $3, $4.",
 -      "wlshowlast": "Show last $1 hours $2 days $3",
 +      "wlshowlast": "Show last $1 hours $2 days",
        "watchlist-options": "Watchlist options",
        "watching": "Watching...",
        "unwatching": "Unwatching...",
        "exif-urgency-low": "Low ($1)",
        "exif-urgency-high": "High ($1)",
        "exif-urgency-other": "User-defined priority ($1)",
 -      "watchlistall2": "all",
        "namespacesall": "all",
        "monthsall": "all",
        "confirmemail": "Confirm email address",
        "mediastatistics-header-office": "Office",
        "mediastatistics-header-text": "Textual",
        "mediastatistics-header-executable": "Executables",
 -      "mediastatistics-header-archive": "Compressed formats"
 +      "mediastatistics-header-archive": "Compressed formats",
 +      "json-warn-trailing-comma": "$1 trailing {{PLURAL:$1|comma was|commas were}} removed from JSON",
 +      "json-error-unknown": "There was a problem with the JSON. Error: $1",
 +      "json-error-depth": "The maximum stack depth has been exceeded",
 +      "json-error-state-mismatch": "Invalid or malformed JSON",
 +      "json-error-ctrl-char": "Control character error, possibly incorrectly encoded",
 +      "json-error-syntax": "Syntax error",
 +      "json-error-utf8": "Malformed UTF-8 characters, possibly incorrectly encoded",
 +      "json-error-recursion": "One or more recursive references in the value to be encoded",
 +      "json-error-inf-or-nan": "One or more NAN or INF values in the value to be encoded",
 +      "json-error-unsupported-type": "A value of a type that cannot be encoded was given"
  }
diff --combined languages/i18n/qqq.json
                        "פוילישער",
                        "គីមស៊្រុន",
                        "아라",
 -                      "Jdforrester"
 +                      "Jdforrester",
 +                      "Mar(c)"
                ]
        },
        "sidebar": "{{notranslate}}",
        "userlogin-resetpassword-link": "Used as link text in the login form.\n\nThe link points to the local page [[Special:PasswordReset]].\n\nSee example: [[Special:UserLogin]]\n\nuserlogin-resetpassword-link may have to be shorter than the old {{msg-mw|userlogin-resetlink}}.\n{{Identical|Forgot your password}}",
        "helplogin-url": "{{doc-important|Do not translate the namespace name <code>Help</code>.}}\nUsed as name of the page that provides information about logging into the wiki.\n\nUsed as a link target in the message {{msg-mw|Userlogin-helplink}}.",
        "userlogin-helplink2": "Label for a link to login help.\n\nSee example: [[Special:UserLogin]]\n\nSee also:\n* {{msg-mw|Helplogin-url}}",
 -      "userlogin-loggedin": "Used as warning on [[Special:UserLogin]] when the current user is already logged in.\n\nFollowed by the Login form.\n\nSee example: [[Special:UserLogin]].\n\nParameters:\n* $1 - user name (used for display and for gender support)",
 +      "userlogin-loggedin": "Used as warning on [[Special:UserLogin]] when the current user is already logged in.\n\nFollowed by the Login form.\n\nSee example: [[Special:UserLogin]].\n\nParameters:\n* $1 - user name (used for display and for gender support)\nSee also:\n* {{msg-mw|Mobile-frontend-userlogin-loggedin-register}}",
        "userlogin-createanother": "Used as label for the button on [[Special:UserLogin]] shown when the current user is already logged in.\n{{Identical|Create another account}}",
        "createacct-emailrequired": "Label in create account form for email field when it is required.\n\nSee also:\n* {{msg-mw|Createacct-emailoptional}}\n{{Identical|E-mail address}}",
        "createacct-emailoptional": "Label in vertical-layout create account form for email field when it is optional.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]\n\nSee also:\n* {{msg-mw|Createacct-emailrequired}}",
        "blocked-mailpassword": "Used as error message in password recovery.",
        "eauthentsent": "This message appears after entering an email address in [[Special:Preferences]] > {{int:prefs-personal}} > {{int:email}}, then clicking on \"{{int:saveprefs}}\".",
        "throttled-mailpassword": "Used in [[Special:PasswordReset]]. Parameters:\n* $1 - password reset email resend time (in hours)",
 -      "loginstart": "{{notranslate}}",
 -      "loginend": "{{notranslate}}",
 -      "loginend-https": "{{notranslate}}",
 -      "signupstart": "{{notranslate}}\nDefault:\n* {{msg-mw|Loginstart}}",
 -      "signupend": "{{notranslate}}\nDefault:\n* {{msg-mw|Loginend}}",
 +      "signupstart": "{{notranslate}}",
 +      "signupend": "{{notranslate}}",
        "signupend-https": "{{notranslate}}",
        "mailerror": "Used as error message in sending confirmation mail to user. Parameters:\n* $1 - new mail address",
        "acct_creation_throttle_hit": "Error message at [[Special:CreateAccount]].\n\n\"in the last day\" precisely means: during the lasts 86400 seconds (24 hours) ending right now.\n\nParameters:\n* $1 - number of accounts",
        "pager-older-n": "This is part of the navigation message on the top and bottom of Special pages which are lists of things in date order, e.g. the User's contributions page. It is passed as the first argument of {{msg-mw|Viewprevnext}}. $1 is the number of items shown per page.",
        "suppress": "{{Identical|Oversight}}",
        "querypage-disabled": "On special pages that use expensive database queries but are not cacheable, this message is displayed when 'miser mode' is on (i.e. no expensive queries allowed).",
+       "apihelp": "{{doc-special|ApiHelp}}",
+       "apihelp-summary": "{{doc-specialpagesummary|ApiHelp}}",
+       "apihelp-no-such-module": "Used as an error message if the requested API module is not found.\n\nParameters:\n* $1 - Requested module name",
+       "apihelp-link": "{{notranslate}} Used to construct a link to [[Special:ApiHelp]]\n\nParameters:\n* $1 - module to link\n* $2 - link text",
        "booksources": "{{doc-special|BookSources}}\n\n'''This message shouldn't be changed unless it has serious mistakes.'''\n\nIt's used as the page name of the configuration page of [[Special:BookSources]]. Changing it breaks existing sites using the default version of this message.\n\nSee also:\n* {{msg-mw|Booksources|title}}\n* {{msg-mw|Booksources-text|text}}",
        "booksources-summary": "{{doc-specialpagesummary|booksources}}",
        "booksources-search-legend": "Box heading on [[Special:BookSources|book sources]] special page. The box is for searching for places where a particular book can be bought or viewed.",
        "wlheader-enotif": "Message at the top of [[Special:Watchlist]], after {{msg-mw|watchlist-details}}. Has to be a full sentence.\n\nSee also:\n* {{msg-mw|Watchlist-options|fieldset}}\n* {{msg-mw|enotif reset|Submit button text}}",
        "wlheader-showupdated": "Message at the top of [[Special:Watchlist]], after {{msg-mw|watchlist-details}}. Has to be a full sentence.",
        "wlnote": "Used on [[Special:Watchlist]] when a maximum number of hours or days is specified.\n\nParameters:\n* $1 - the number of changes shown\n* $2 - the number of hours for which the changes are shown\n* $3 - a date alone\n* $4 - a time alone",
 -      "wlshowlast": "Appears on [[Special:Watchlist]]. Parameters:\n* $1 - a choice of different numbers of hours (\"1 | 2 | 6 | 12\")\n* $2 - a choice of different numbers of days (\"1 | 3 | 7\")\n* $3 - {{msg-mw|watchlistall2}}\nClicking on your choice changes the list of changes you see (without changing the default in my preferences).",
 +      "wlshowlast": "Appears on [[Special:Watchlist]]. Parameters:\n* $1 - a choice of different numbers of hours (\"1 | 2 | 6 | 12\")\n* $2 - a choice of different numbers of days (\"1 | 3 | 7\" and the maximum number of days available)\nClicking on your choice changes the list of changes you see (without changing the default in my preferences).",
        "watchlist-options": "Legend of the fieldset of [[Special:Watchlist]]\n\nSee also:\n* {{msg-mw|Watchlist-details|watchlist header}}\n* {{msg-mw|Wlheader-enotif|watchlist header}}\n* {{msg-mw|enotif reset|Submit button text}}",
        "watching": "Text displayed when clicked on the watch tab: {{msg-mw|Watch}}. It means the wiki is adding that page to your watchlist.",
        "unwatching": "Text displayed when clicked on the unwatch tab: {{msg-mw|Unwatch}}. It means the wiki is removing that page from your watchlist.",
        "contribsub2": "Contributions for \"user\" (links). Parameters:\n* $1 is an IP address or a username, with a link which points to the user page (if registered user).\n* $2 is list of tool links. The list contains a link which has text {{msg-mw|Sp-contributions-talk}}.\n* $3 is a plain text username used for GENDER.\n{{Identical|For $1}}",
        "contributions-userdoesnotexist": "This message is used in [[Special:Contributions]]. It is used to tell the user that the name he searched for doesn't exists.\n\nParameters:\n* $1 - a username\n\n{{identical|userdoesnotexist}}",
        "nocontribs": "Used in [[Special:Contributions]] and [[Special:DeletedContributions]].\n\nSee examples: [[Special:Contributions/x]] and [[Special:DeletedContributions/x]].\n\nParameters:\n* $1 - (Unused) the user name",
 -      "uctop": "This message is used in [[Special:Contributions]]. It is used to show that a particular edit was the last made to a page. Example: 09:57, 11 February 2008 (hist) (diff) Pagename‎ (edit summary) (current)\n{{Identical|Top}}",
 +      "uctop": "This message is used in [[Special:Contributions]]. It is used to show that a particular edit was the last made to a page. Example: 09:57, 11 February 2008 (hist) (diff) Pagename‎ (edit summary) (current)\n{{Identical|Current}}",
        "month": "Used in [[Special:Contributions]] and history pages ([{{fullurl:Sandbox|action=history}} example]), as label for a dropdown box to select a specific month to view the edits made in that month, and the earlier months. See also {{msg-mw|year}}.",
        "year": "Used in [[Special:Contributions]] and history pages ([{{fullurl:Sandbox|action=history}} example]), as label for an input box to select a specific year to view the edits made in that year, and the earlier years.\n\nSee also:\n* {{msg-mw|month}}",
        "sp-contributions-newbies": "Text of radio button on special page [[Special:Contributions]].",
        "tooltip-pt-preferences": "Tooltip shown when hovering over the {{msg-mw|Mypreferences}} link in your personal toolbox (upper right side).\n\nSee also:\n* {{msg-mw|Mypreferences}}\n* {{msg-mw|Accesskey-pt-preferences}}\n* {{msg-mw|Tooltip-pt-preferences}}\n{{Identical|Preferences}}",
        "tooltip-pt-watchlist": "Tooltip shown when hovering over the {{msg-mw|Mywatchlist}} link in your personal toolbox (upper right side).\n\nSee also:\n* {{msg-mw|Mywatchlist}}\n* {{msg-mw|Accesskey-pt-watchlist}}\n* {{msg-mw|Tooltip-pt-watchlist}}",
        "tooltip-pt-mycontris": "Tooltip shown when hovering over the {{msg-mw|Mycontris}} link in your personal toolbox (upper right side).\n\nSee also:\n* {{msg-mw|Mycontris}}\n* {{msg-mw|Accesskey-pt-mycontris}}\n* {{msg-mw|Tooltip-pt-mycontris}}",
 -      "tooltip-pt-login": "Tooltip shown when hovering over the link 'Log in / create account' in the upper right corner show on all pages while not logged in.",
 +      "tooltip-pt-login": "Tooltip shown when hovering over the link 'Log in' in the upper right corner show on all pages while not logged in.",
        "tooltip-pt-logout": "Tooltip shown when hovering over the {{msg-mw|Logout}} link in your personal toolbox (upper right side).\n\nSee also:\n* {{msg-mw|Logout}}\n* {{msg-mw|Accesskey-pt-logout}}\n* {{msg-mw|Tooltip-pt-logout}}\n{{Identical|Log out}}",
        "tooltip-ca-talk": "Tooltip shown when hovering over the {{msg-mw|Talk}} tab.\n\nA 'content page' is a page that forms part of the purpose of the wiki. It includes the main page and pages in the main namespace and any other namespaces that are included when the wiki is customised. For example on Wikimedia Commons 'content pages' include pages in the file and category namespaces. On Wikinews 'content pages' include pages in the Portal namespace. For a technical definition of 'content namespaces' see [[mw:Manual:Using_custom_namespaces#Content_namespaces|MediaWiki]].\n\nPossible alternatives to the word 'content' are 'subject matter' or 'wiki subject' or 'wiki purpose'.\n\nSee also:\n* {{msg-mw|Talk}}\n* {{msg-mw|Accesskey-ca-talk}}\n* {{msg-mw|Tooltip-ca-talk}}\n{{Identical|Content page}}",
        "tooltip-ca-edit": "The tooltip when hovering over the {{msg-mw|Edit}} tab.\n\nSee also:\n* {{msg-mw|Edit}}\n* {{msg-mw|Accesskey-ca-edit}}\n* {{msg-mw|Tooltip-ca-edit}}",
        "exif-urgency-low": "Parameters:\n* $1 - numeric priority (6-8 for low)\n{{Related|Exif-urgency}}",
        "exif-urgency-high": "Parameters:\n* $1 - numeric priority (1-4 for high)\n{{Related|Exif-urgency}}",
        "exif-urgency-other": "Parameters:\n* $1 - numeric priority. Most specs define 0 and 9 to either be reserved or not allowed. However the exiftool documentation defines 0 to be reserved and 9 to be user-defined priority.\n{{Related|Exif-urgency}}",
 -      "watchlistall2": "Appears on [[Special:Watchlist]].\n\nUsed as <code>$3</code> in the message {{msg-mw|Wlshowlast}}.\n{{Identical|All}}",
        "namespacesall": "In special page [[Special:WhatLinksHere]]. Drop-down box option for namespace.\n\n{{Identical|All}}",
        "monthsall": "Used in a drop-down box on [[Special:Contributions]] as an option for \"all months\". See also {{msg-mw|Month}}.\n{{Identical|All}}",
        "confirmemail": "Title of [[Special:ConfirmEmail]] page.",
        "revdelete-unhid": "This message is used as <code>$1</code>:\n* in {{msg-mw|Revdelete-log-message}} when unhiding revisions\n* in {{msg-mw|Logdelete-log-message}} when unhiding information in the log entry about unhiding revisions\nParameters:\n* $1 - any one of the following:\n** {{msg-mw|Revdelete-content}} (when unhiding the page content)\n** {{msg-mw|Revdelete-summary}} (when unhiding the edit summary)\n** {{msg-mw|Revdelete-uname}} (when unhiding the user name)\n** a combination of these three messages\nSee also:\n* {{msg-mw|Revdelete-hid}}",
        "revdelete-log-message": "This log message is used together with {{msg-mw|revdelete-logentry}} in the deletion or suppression logs when changing visibility restrictions for page revisions.\n\nParameters:\n* $1 - any one of the following\n** {{msg-mw|revdelete-hid}} (when hiding data)\n** {{msg-mw|revdelete-unhid}} (when unhiding data)\n** {{msg-mw|revdelete-restricted}} (when applying restrictions for sysops)\n** {{msg-mw|revdelete-unrestricted}} (when removing restrictions for sysops)\n** a combination of those messages\n* $2 - the number of revisions for which the restrictions are changed\n\nPlease note that the parameters in a log entry will appear in the log only in the default language of the wiki. View [[Special:Log]] for examples on translatewiki.net with English default language.\n\nSee also:\n* {{msg-mw|Logdelete-log-message}}",
        "logdelete-log-message": "This log message appears in brackets after the message {{msg-mw|logdelete-logentry}} in the deletion or suppression logs when changing the visibility of a log entry for events. For a brief description of the process of changing the visibility of events and their log entries see this [http://www.mediawiki.org/wiki/RevisionDelete mediawiki explanation].\n\nParameters:\n* $1 - any one of the following\n** {{msg-mw|revdelete-hid}} (when hiding data)\n** {{msg-mw|revdelete-unhid}} (when unhiding data)\n** {{msg-mw|revdelete-restricted}} (when applying restrictions for sysops)\n** {{msg-mw|revdelete-unrestricted}} (when removing restrictions for sysops)\n** a combination of those messages\n* $2 - the number of events for which the restrictions are changed\n\nPlease note that the parameters in a log entry will appear in the log only in the default language of the wiki. View [[Special:Log]] for examples on translatewiki.net with English default language.\n\nSee also:\n* {{msg-mw|Revdelete-log-message}}",
 -      "deletedarticle": "This is a ''logentry'' message. Parameters:\n* $1 - deleted page name",
 +      "deletedarticle": "This is a ''logentry'' message. Parameters:\n* $1 - deleted page name\n{{Identical|Deleted}}",
        "suppressedarticle": "Part of a [[mw:Manual:RevisionDelete|RevisionDelete]] log entry. Parameters:\n* $1 - suppressed page name\n{{Identical|Suppressed}}",
        "undeletedarticle": "This is a ''logentry'' message. Parameters:\n* $1 - restored (undeleted) page name",
        "patrol-log-line": "Text of notes on entries in the [http://translatewiki.net/w/i.php?title=Special%3ALog&type=patrol&user=&page=&year=&month=-1 patrol log].\nParameters:\n* $1 - the link whose text is {{msg-mw|patrol-log-diff}}\n* $2 - the name of the page\n* $3 - appears to be {{msg-mw|Patrol-log-auto}} (at least sometimes)\n\nThe message appears after the name of the patroller.",
        "mediastatistics-header-office": "Header on [[Special:MediaStatistics]] for file types that are in the Office category. This includes PDFs, OpenDocument files, Microsoft Word files, etc.",
        "mediastatistics-header-text": "Header on [[Special:MediaStatistics]] for file types that are in the text category. This includes simple text formats, including plain text formats, json, csv, and xml. Source code of compiled programming languages may be included here in the future, but isn't currently.",
        "mediastatistics-header-executable": "Header on [[Special:MediaStatistics]] for file types that are in the executable category. This includes things like source files for interpreted programming language (Shell scripts, javascript, etc).",
 -      "mediastatistics-header-archive": "Header on [[Special:MediaStatistics]] for file types that are in the archive category. Includes things like tar, zip, gzip etc."
 +      "mediastatistics-header-archive": "Header on [[Special:MediaStatistics]] for file types that are in the archive category. Includes things like tar, zip, gzip etc.",
 +      "json-warn-trailing-comma": "A warning message notifying that JSON text was automatically corrected by removing erroneous commas.\n\nParameters:\n* $1 - number of commas that were removed\n{{Related|Json-error}}",
 +      "json-error-unknown": "User error message when there’s an unknown error.\n\nThis error is shown if we received an unexpected value from PHP. See http://php.net/manual/en/function.json-last-error.php\n\nParameters:\n* $1 - integer error code\n{{Related|Json-error}}\n{{Identical|Unknown error}}",
 +      "json-error-depth": "User error message when the maximum stack depth is exceeded.\nSee http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
 +      "json-error-state-mismatch": "User error message when underflow or the modes mismatch.\n\n'''Underflow''': A data-processing error arising when the absolute value of a computed quantity is smaller than the limits of precision of the computing device, retaining at least one significant digit.\n\nSee http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
 +      "json-error-ctrl-char": "User error message when an unexpected control character has been found.\nSee http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
 +      "json-error-syntax": "User error message when there is a syntax error; a malformed JSON.\nSee http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}\n{{Identical|Syntax error}}",
 +      "json-error-utf8": "User error message when there are malformed UTF-8 characters, possibly incorrectly encoded.\nSee http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
 +      "json-error-recursion": "PHP JSON encoding/decoding error. See http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
 +      "json-error-inf-or-nan": "PHP JSON encoding/decoding error. See http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
 +      "json-error-unsupported-type": "PHP JSON encoding/decoding error. See http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}"
  }
diff --combined resources/Resources.php
@@@ -93,7 -93,6 +93,7 @@@ return array
                ),
        ),
        'mediawiki.skinning.interface' => array(
 +              'class' => 'ResourceLoaderSkinModule',
                // Used in the web installer. Test it after modifying this definition!
                'styles' => array(
                        'resources/src/mediawiki.skinning/elements.css' => array( 'media' => 'screen' ),
        'jquery.hoverIntent' => array(
                'scripts' => 'resources/lib/jquery/jquery.hoverIntent.js',
        ),
 -      'jquery.json' => array(
 -              // @deprecated since 1.24: Use the 'json' module and global JSON object instead.
 -              'scripts' => array(
 -                      'resources/lib/jquery/jquery.json.js',
 -                      'resources/src/jquery.json-deprecate.js',
 -              ),
 -              'targets' => array( 'mobile', 'desktop' ),
 -      ),
        'jquery.localize' => array(
                'scripts' => 'resources/src/jquery/jquery.localize.js',
        ),
                'raw' => true,
                'targets' => array( 'desktop', 'mobile' ),
        ),
+       'mediawiki.apihelp' => array(
+               'styles' => 'resources/src/mediawiki/mediawiki.apihelp.css',
+               'targets' => array( 'desktop' ),
+               'dependencies' => array(
+                       'mediawiki.hlist',
+               ),
+       ),
        'mediawiki.api' => array(
                'scripts' => 'resources/src/mediawiki.api/mediawiki.api.js',
                'dependencies' => 'mediawiki.util',
        'mediawiki.content.json' => array(
                'styles' => 'resources/src/mediawiki/mediawiki.content.json.css',
        ),
 +      'mediawiki.confirmCloseWindow' => array(
 +              'scripts' => array(
 +                      'resources/src/mediawiki/mediawiki.confirmCloseWindow.js',
 +              ),
 +      ),
        'mediawiki.debug' => array(
                'scripts' => array(
                        'resources/src/mediawiki/mediawiki.debug.js',
                ),
                'targets' => array( 'desktop', 'mobile' ),
        ),
 +      'mediawiki.userSuggest' => array(
 +              'scripts' => 'resources/src/mediawiki/mediawiki.userSuggest.js',
 +              'dependencies' => array(
 +                      'jquery.suggestions',
 +                      'mediawiki.api'
 +              )
 +      ),
        'mediawiki.util' => array(
                'scripts' => 'resources/src/mediawiki/mediawiki.util.js',
                'dependencies' => array(
                        'jquery.cookie',
                ),
        ),
 +      'mediawiki.toolbar' => array(
 +              'class' => 'ResourceLoaderEditToolbarModule',
 +              'scripts' => 'resources/src/mediawiki.toolbar/toolbar.js',
 +              'styles' => 'resources/src/mediawiki.toolbar/toolbar.less',
 +      ),
  
        /* MediaWiki Action */
  
                'styles' => 'resources/src/mediawiki.action/mediawiki.action.edit.css',
                'dependencies' => array(
                        'mediawiki.action.edit.styles',
 -                      'mediawiki.action.edit.toolbar',
 +                      'mediawiki.toolbar',
                        'jquery.textSelection',
                        'jquery.byteLimit',
                ),
                'styles' => 'resources/src/mediawiki.action/mediawiki.action.edit.styles.css',
                'position' => 'top',
        ),
 -      'mediawiki.action.edit.toolbar' => array(
 -              'class' => 'ResourceLoaderEditToolbarModule',
 -              'styles' => 'resources/src/mediawiki.action/mediawiki.action.edit.toolbar/mediawiki.action.edit.toolbar.less',
 -      ),
        'mediawiki.action.edit.collapsibleFooter' => array(
                'scripts' => 'resources/src/mediawiki.action/mediawiki.action.edit.collapsibleFooter.js',
                'styles' => 'resources/src/mediawiki.action/mediawiki.action.edit.collapsibleFooter.css',
                'dependencies' => array(
                        'mediawiki.util',
                        'mediawiki.page.startup',
 +                      'user.options',
                ),
        ),
        'mediawiki.action.view.metadata' => array(
                ),
                'position' => 'top',
        ),
 -      // Deployment hack for compatibility with cached HTML, remove before 1.24 release
 -      'mediawiki.action.view.redirectToFragment' => array(
 -              'dependencies' => 'mediawiki.action.view.redirect',
 -              'position' => 'top',
 -      ),
        'mediawiki.action.view.redirectPage' => array(
                'styles' => 'resources/src/mediawiki.action/mediawiki.action.view.redirectPage.css',
                'position' => 'top',
                'scripts' => 'resources/src/mediawiki.action/mediawiki.action.edit.editWarning.js',
                'dependencies' => array(
                        'jquery.textSelection',
 -                      'mediawiki.jqueryMsg'
 +                      'mediawiki.jqueryMsg',
 +                      'mediawiki.confirmCloseWindow',
                ),
                'messages' => array(
                        'editwarning-warning',
 +                      // editwarning-warning uses {{int:prefs-editing}}
                        'prefs-editing'
                ),
        ),
                        'uk' => 'resources/src/mediawiki.language/languages/uk.js',
                ),
                'dependencies' => array(
 -                              'mediawiki.language.data',
 -                              'mediawiki.cldr',
 -                      ),
 +                      'mediawiki.language.data',
 +                      'mediawiki.cldr',
 +              ),
                'targets' => array( 'desktop', 'mobile' ),
                'messages' => array(
                        'and',
                        'size-megabytes',
                        'size-gigabytes',
                        'largefileserver',
 +                      'editwarning-warning',
 +                      // editwarning-warning uses {{int:prefs-editing}}
 +                      'prefs-editing',
                ),
                'dependencies' => array(
                        'jquery.spinner',
 +                      'mediawiki.jqueryMsg',
                        'mediawiki.api',
                        'mediawiki.libs.jpegmeta',
                        'mediawiki.Title',
                        'mediawiki.util',
 +                      'mediawiki.confirmCloseWindow',
                ),
        ),
        'mediawiki.special.userlogin.common.styles' => array(
                'targets' => array( 'desktop', 'mobile' ),
        ),
        'mediawiki.ui.icon' => array(
 -              'styles' => array(
 -                      'resources/src/mediawiki.ui/components/icons.less',
 +              'skinStyles' => array(
 +                      'default' => array(
 +                              'resources/src/mediawiki.ui/components/icons.less',
 +                      ),
 +              ),
 +              'position' => 'top',
 +              'targets' => array( 'desktop', 'mobile' ),
 +      ),
 +      // Lightweight module for text styles
 +      'mediawiki.ui.text' => array(
 +              'skinStyles' => array(
 +                      'default' => array(
 +                              'resources/src/mediawiki.ui/components/text.less',
 +                      ),
                ),
                'position' => 'top',
                'targets' => array( 'desktop', 'mobile' ),
                'scripts' => array(
                        'resources/lib/oojs-ui/oojs-ui.js',
                ),
 -              'styles' => array(
 -                      'resources/lib/oojs-ui/oojs-ui.svg.css',
 +              'skinScripts' => array(
 +                      'default' => 'resources/lib/oojs-ui/oojs-ui-apex.js',
 +                      'minerva' => 'resources/lib/oojs-ui/oojs-ui-mediawiki.js',
                ),
                'skinStyles' => array(
 -                      'default' => 'resources/lib/oojs-ui/oojs-ui-apex.css',
 -                      // FIXME As of July 2014, this is to be gone "in a couple of months".
 -                      'minerva' => 'resources/lib/oojs-ui/oojs-ui-minerva.css',
 +                      'default' => 'resources/lib/oojs-ui/oojs-ui-apex.svg.css',
 +                      'minerva' => 'resources/lib/oojs-ui/oojs-ui-mediawiki.svg.css',
                ),
                'messages' => array(
                        'ooui-outline-control-move-down',