Merge "REST: call MediaWiki::preOutputCommit and doPostOutputShutdown"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 26 Aug 2019 23:36:36 +0000 (23:36 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 26 Aug 2019 23:36:36 +0000 (23:36 +0000)
69 files changed:
RELEASE-NOTES-1.34
autoload.php
includes/DefaultSettings.php
includes/Linker.php
includes/MediaWikiServices.php
includes/OutputPage.php
includes/ServiceWiring.php
includes/Setup.php
includes/Title.php
includes/actions/InfoAction.php
includes/actions/pagers/HistoryPager.php
includes/api/ApiPageSet.php
includes/api/ApiQueryAllDeletedRevisions.php
includes/api/i18n/ar.json
includes/api/i18n/sv.json
includes/api/i18n/zh-hans.json
includes/api/i18n/zh-hant.json
includes/block/BlockManager.php
includes/cache/localisation/LocalisationCache.php
includes/filebackend/FileBackendGroup.php
includes/installer/Installer.php
includes/installer/SqliteInstaller.php
includes/installer/i18n/da.json
includes/installer/i18n/id.json
includes/installer/i18n/ro.json
includes/installer/i18n/sh.json
includes/language/ConverterRule.php [new file with mode: 0644]
includes/language/LanguageCode.php
includes/language/LanguageNameUtils.php [deleted file]
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/objectcache/SqlBagOStuff.php
includes/pager/IndexPager.php
includes/pager/TablePager.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialNewSection.php
includes/specials/SpecialRecentChanges.php
includes/specials/pagers/AllMessagesTablePager.php
includes/specials/pagers/BlockListPager.php
includes/specials/pagers/CategoryPager.php
includes/specials/pagers/ContribsPager.php
includes/specials/pagers/DeletedContribsPager.php
includes/specials/pagers/ImageListPager.php
includes/specials/pagers/NewFilesPager.php
includes/specials/pagers/ProtectedPagesPager.php
includes/specials/pagers/UsersPager.php
languages/ConverterRule.php [deleted file]
languages/Language.php
languages/data/Names.php
languages/i18n/bcc.json
languages/i18n/exif/ro.json
languages/i18n/exif/szl.json
languages/i18n/exif/tt-cyrl.json
languages/i18n/fa.json
languages/i18n/io.json
languages/i18n/it.json
languages/i18n/sd.json
languages/i18n/te.json
languages/i18n/zh-hk.json
maintenance/rebuildLocalisationCache.php
tests/common/TestsAutoLoader.php
tests/phpunit/MediaWikiIntegrationTestCase.php
tests/phpunit/includes/TitlePermissionTest.php
tests/phpunit/includes/api/ApiQuerySiteinfoTest.php
tests/phpunit/includes/cache/LocalisationCacheTest.php
tests/phpunit/includes/language/ConverterRuleTest.php [new file with mode: 0644]
tests/phpunit/includes/logging/LogFormatterTest.php
tests/phpunit/languages/LanguageTest.php
tests/phpunit/unit/includes/language/LanguageNameUtilsTest.php [deleted file]
tests/phpunit/unit/includes/language/LanguageNameUtilsTestTrait.php [deleted file]

index df25d30..620a6f5 100644 (file)
@@ -354,8 +354,6 @@ because of Phabricator reports.
 * The UserIsBlockedFrom hook is only called if a block is found first, and
   should only be used to unblock a blocked user.
 * …
-* Language::$dataCache has been removed (without prior deprecation, for
-  practical reasons). Use MediaWikiServices instead to get a LocalisationCache.
 
 === Deprecations in 1.34 ===
 * The MWNamespace class is deprecated. Use NamespaceInfo.
@@ -465,12 +463,6 @@ because of Phabricator reports.
 * Constructing MovePage directly is deprecated. Use MovePageFactory.
 * TempFSFile::factory() has been deprecated. Use TempFSFileFactory instead.
 * wfIsBadImage() is deprecated. Use the BadFileLookup service instead.
-* Language::getLocalisationCache() is deprecated. Use MediaWikiServices.
-* The following Language methods are deprecated: isSupportedLanguage,
-  isValidCode, isValidBuiltInCode, isKnownLanguageTag, fetchLanguageNames,
-  fetchLanguageName, getFileName, getMessagesFileName, getJsonMessagesFileName.
-  Use the new LanguageNameUtils class instead. (Note that fetchLanguageName(s)
-  are called getLanguageName(s) in the new class.)
 
 === Other changes in 1.34 ===
 * …
index dfdf802..60bc5b5 100644 (file)
@@ -316,7 +316,7 @@ $wgAutoloadLocalClasses = [
        'ConvertExtensionToRegistration' => __DIR__ . '/maintenance/convertExtensionToRegistration.php',
        'ConvertLinks' => __DIR__ . '/maintenance/convertLinks.php',
        'ConvertUserOptions' => __DIR__ . '/maintenance/convertUserOptions.php',
-       'ConverterRule' => __DIR__ . '/languages/ConverterRule.php',
+       'ConverterRule' => __DIR__ . '/includes/language/ConverterRule.php',
        'Cookie' => __DIR__ . '/includes/libs/Cookie.php',
        'CookieJar' => __DIR__ . '/includes/libs/CookieJar.php',
        'CopyFileBackend' => __DIR__ . '/maintenance/copyFileBackend.php',
@@ -892,7 +892,6 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Languages\\Data\\CrhExceptions' => __DIR__ . '/languages/data/CrhExceptions.php',
        'MediaWiki\\Languages\\Data\\Names' => __DIR__ . '/languages/data/Names.php',
        'MediaWiki\\Languages\\Data\\ZhConversion' => __DIR__ . '/languages/data/ZhConversion.php',
-       'MediaWiki\\Languages\\LanguageNameUtils' => __DIR__ . '/includes/language/LanguageNameUtils.php',
        'MediaWiki\\Logger\\ConsoleLogger' => __DIR__ . '/includes/debug/logger/ConsoleLogger.php',
        'MediaWiki\\Logger\\ConsoleSpi' => __DIR__ . '/includes/debug/logger/ConsoleSpi.php',
        'MediaWiki\\Logger\\LegacyLogger' => __DIR__ . '/includes/debug/logger/LegacyLogger.php',
index 8341dac..7e13fc2 100644 (file)
@@ -2636,8 +2636,6 @@ $wgLocalisationCacheConf = [
        'store' => 'detect',
        'storeClass' => false,
        'storeDirectory' => false,
-       'storeServer' => [],
-       'forceRecache' => false,
        'manualRecache' => false,
 ];
 
@@ -6844,6 +6842,8 @@ $wgRCLinkLimits = [ 50, 100, 250, 500 ];
 /**
  * List of Days options to list in the Special:Recentchanges and
  * Special:Recentchangeslinked pages.
+ *
+ * @see ChangesListSpecialPage::getLinkDays
  */
 $wgRCLinkDays = [ 1, 3, 7, 14, 30 ];
 
index 47be8a2..a79ec3a 100644 (file)
@@ -1322,7 +1322,7 @@ class Linker {
                                        $services->getNamespaceInfo()->getCanonicalName( NS_MEDIA ), '/' );
                                $medians .= '|';
                                $medians .= preg_quote(
-                                       MediaWikiServices::getInstance()->getContentLanguage()->getNsText( NS_MEDIA ),
+                                       $services->getContentLanguage()->getNsText( NS_MEDIA ),
                                        '/'
                                ) . '):';
 
@@ -1359,7 +1359,7 @@ class Linker {
                                        }
                                        if ( $match[1] !== false && $match[1] !== '' ) {
                                                if ( preg_match(
-                                                       MediaWikiServices::getInstance()->getContentLanguage()->linkTrail(),
+                                                       $services->getContentLanguage()->linkTrail(),
                                                        $match[3],
                                                        $submatch
                                                ) ) {
@@ -1375,7 +1375,7 @@ class Linker {
 
                                                Title::newFromText( $linkTarget );
                                                try {
-                                                       $target = MediaWikiServices::getInstance()->getTitleParser()->
+                                                       $target = $services->getTitleParser()->
                                                                parseTitle( $linkTarget );
 
                                                        if ( $target->getText() == '' && !$target->isExternal()
index e926c32..6013aaf 100644 (file)
@@ -14,12 +14,10 @@ use GlobalVarConfig;
 use Hooks;
 use IBufferingStatsdDataFactory;
 use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
-use LocalisationCache;
 use MediaWiki\Block\BlockManager;
 use MediaWiki\Block\BlockRestrictionStore;
 use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
 use MediaWiki\Http\HttpRequestFactory;
-use MediaWiki\Languages\LanguageNameUtils;
 use MediaWiki\Page\MovePageFactory;
 use MediaWiki\Permissions\PermissionManager;
 use MediaWiki\Preferences\PreferencesFactory;
@@ -625,14 +623,6 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'InterwikiLookup' );
        }
 
-       /**
-        * @since 1.34
-        * @return LanguageNameUtils
-        */
-       public function getLanguageNameUtils() {
-               return $this->getService( 'LanguageNameUtils' );
-       }
-
        /**
         * @since 1.28
         * @return LinkCache
@@ -660,14 +650,6 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'LinkRendererFactory' );
        }
 
-       /**
-        * @since 1.34
-        * @return LocalisationCache
-        */
-       public function getLocalisationCache() : LocalisationCache {
-               return $this->getService( 'LocalisationCache' );
-       }
-
        /**
         * @since 1.28
         * @return \BagOStuff
index 3f2dcf7..b2ca53a 100644 (file)
@@ -3227,7 +3227,7 @@ class OutputPage extends ContextSource {
 
                $title = $this->getTitle();
                $ns = $title->getNamespace();
-               $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
+               $nsInfo = $services->getNamespaceInfo();
                $canonicalNamespace = $nsInfo->exists( $ns )
                        ? $nsInfo->getCanonicalName( $ns )
                        : $title->getNsText();
index 21a66cd..b307264 100644 (file)
@@ -48,7 +48,6 @@ use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
 use MediaWiki\Http\HttpRequestFactory;
 use MediaWiki\Interwiki\ClassicInterwikiLookup;
 use MediaWiki\Interwiki\InterwikiLookup;
-use MediaWiki\Languages\LanguageNameUtils;
 use MediaWiki\Linker\LinkRenderer;
 use MediaWiki\Linker\LinkRendererFactory;
 use MediaWiki\Logger\LoggerFactory;
@@ -257,13 +256,6 @@ return [
                );
        },
 
-       'LanguageNameUtils' => function ( MediaWikiServices $services ) : LanguageNameUtils {
-               return new LanguageNameUtils( new ServiceOptions(
-                       LanguageNameUtils::$constructorOptions,
-                       $services->getMainConfig()
-               ) );
-       },
-
        'LinkCache' => function ( MediaWikiServices $services ) : LinkCache {
                return new LinkCache(
                        $services->getTitleFormatter(),
@@ -290,56 +282,6 @@ return [
                );
        },
 
-       'LocalisationCache' => function ( MediaWikiServices $services ) : LocalisationCache {
-               $conf = $services->getMainConfig()->get( 'LocalisationCacheConf' );
-
-               $logger = LoggerFactory::getInstance( 'localisation' );
-
-               // Figure out what class to use for the LCStore
-               $storeArg = [];
-               $storeArg['directory'] =
-                       $conf['storeDirectory'] ?? $services->getMainConfig()->get( 'CacheDirectory' );
-
-               if ( !empty( $conf['storeClass'] ) ) {
-                       $storeClass = $conf['storeClass'];
-               } elseif ( $conf['store'] === 'files' || $conf['store'] === 'file' ||
-                       ( $conf['store'] === 'detect' && $storeArg['directory'] )
-               ) {
-                       $storeClass = LCStoreCDB::class;
-               } elseif ( $conf['store'] === 'db' || $conf['store'] === 'detect' ) {
-                       $storeClass = LCStoreDB::class;
-                       $storeArg['server'] = $conf['storeServer'] ?? [];
-               } elseif ( $conf['store'] === 'array' ) {
-                       $storeClass = LCStoreStaticArray::class;
-               } else {
-                       throw new MWException(
-                               'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
-                       );
-               }
-               $logger->debug( "LocalisationCache: using store $storeClass" );
-
-               return new $conf['class'](
-                       new ServiceOptions(
-                               LocalisationCache::$constructorOptions,
-                               // Two of the options are stored in $wgLocalisationCacheConf
-                               $conf,
-                               // In case someone set that config variable and didn't reset all keys, set defaults.
-                               [
-                                       'forceRecache' => false,
-                                       'manualRecache' => false,
-                               ],
-                               // Some other options come from config itself
-                               $services->getMainConfig()
-                       ),
-                       new $storeClass( $storeArg ),
-                       $logger,
-                       [ function () use ( $services ) {
-                               $services->getResourceLoader()->getMessageBlobStore()->clear();
-                       } ],
-                       $services->getLanguageNameUtils()
-               );
-       },
-
        'LocalServerObjectCache' => function ( MediaWikiServices $services ) : BagOStuff {
                $config = $services->getMainConfig();
                $cacheId = \ObjectCache::detectLocalServerCache();
index 201e1a9..4717d1a 100644 (file)
@@ -368,19 +368,6 @@ foreach ( $wgForeignFileRepos as &$repo ) {
 unset( $repo ); // no global pollution; destroy reference
 
 $rcMaxAgeDays = $wgRCMaxAge / ( 3600 * 24 );
-if ( $wgRCFilterByAge ) {
-       // Trim down $wgRCLinkDays so that it only lists links which are valid
-       // as determined by $wgRCMaxAge.
-       // Note that we allow 1 link higher than the max for things like 56 days but a 60 day link.
-       sort( $wgRCLinkDays );
-
-       foreach ( $wgRCLinkDays as $i => $days ) {
-               if ( $days >= $rcMaxAgeDays ) {
-                       array_splice( $wgRCLinkDays, $i + 1 );
-                       break;
-               }
-       }
-}
 // Ensure that default user options are not invalid, since that breaks Special:Preferences
 $wgDefaultUserOptions['rcdays'] = min(
        $wgDefaultUserOptions['rcdays'],
index 75ddea8..eb19076 100644 (file)
@@ -3184,7 +3184,7 @@ class Title implements LinkTarget, IDBAccessObject {
        public static function capitalize( $text, $ns = NS_MAIN ) {
                $services = MediaWikiServices::getInstance();
                if ( $services->getNamespaceInfo()->isCapitalized( $ns ) ) {
-                       return MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $text );
+                       return $services->getContentLanguage()->ucfirst( $text );
                } else {
                        return $text;
                }
index 15cee94..7bcfc88 100644 (file)
@@ -280,7 +280,7 @@ class InfoAction extends FormlessAction {
                // Language in which the page content is (supposed to be) written
                $pageLang = $title->getPageLanguage()->getCode();
 
-               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+               $permissionManager = $services->getPermissionManager();
 
                $pageLangHtml = $pageLang . ' - ' .
                        Language::fetchLanguageName( $pageLang, $lang->getCode() );
index 14f76bc..fd2fbd0 100644 (file)
@@ -422,7 +422,7 @@ class HistoryPager extends ReverseChronologicalPager {
                                $undoTooltip = $latest
                                        ? [ 'title' => $this->msg( 'tooltip-undo' )->text() ]
                                        : [];
-                               $undolink = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
+                               $undolink = $this->getLinkRenderer()->makeKnownLink(
                                        $this->getTitle(),
                                        $this->msg( 'editundo' )->text(),
                                        $undoTooltip,
@@ -502,7 +502,7 @@ class HistoryPager extends ReverseChronologicalPager {
                ) {
                        return $cur;
                } else {
-                       return MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
+                       return $this->getLinkRenderer()->makeKnownLink(
                                $this->getTitle(),
                                new HtmlArmor( $cur ),
                                [],
@@ -531,7 +531,7 @@ class HistoryPager extends ReverseChronologicalPager {
                        return $last;
                }
 
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
                if ( $next === 'unknown' ) {
                        # Next row probably exists but is unknown, use an oldid=prev link
                        return $linkRenderer->makeKnownLink(
index 1b58865..c604322 100644 (file)
@@ -1253,7 +1253,7 @@ class ApiPageSet extends ApiBase {
 
                        // Need gender information
                        if (
-                               MediaWikiServices::getInstance()->getNamespaceInfo()->
+                               $services->getNamespaceInfo()->
                                        hasGenderDistinction( $titleObj->getNamespace() )
                        ) {
                                $usernames[] = $titleObj->getText();
index d713b3a..7d6d342 100644 (file)
@@ -134,7 +134,7 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                        $this->addJoinConds(
                                [ 'change_tag' => [ 'JOIN', [ 'ar_rev_id=ct_rev_id' ] ] ]
                        );
-                       $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
+                       $changeTagDefStore = $services->getChangeTagDefStore();
                        try {
                                $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $params['tag'] ) );
                        } catch ( NameTableAccessException $exception ) {
index af97236..dc9c516 100644 (file)
        "apiwarn-deprecation-missingparam": "نظرا لعدم تحديد <var>$1</var>; تم استخدام تنسيق قديم للإخراج، تم إيقاف هذا التنسيق، وسيتم دائما استخدام التنسيق الجديد في المستقبل.",
        "apiwarn-deprecation-parameter": "تم إيقاف الوسيط <var>$1</var>.",
        "apiwarn-deprecation-parse-headitems": "تم إيقاف <kbd>prop=headitems</kbd> منذ ميدياويكي 1.28; استخدم <kbd>prop=headhtml</kbd> عند إنشاء مستندات HTML جديدة، أو <kbd>prop=modules|jsconfigvars</kbd> عند تحديث مستند من جانب العميل.",
+       "apiwarn-deprecation-post-without-content-type": "تم تقديم طلب POST بدون عنوان <code>Content-Type</code>، هذا لا يعمل بشكل موثوق.",
        "apiwarn-deprecation-purge-get": "تم إيقاف استخدام <kbd>action=purge</kbd> عبر GET; استخدم POST بدلا من ذلك.",
        "apiwarn-deprecation-withreplacement": "تم إيقاف <kbd>$1</kbd>; الرجاء استخدام <kbd>$2</kbd> بدلا من ذلك.",
        "apiwarn-difftohidden": "لا يمكنك إجراء مقارنة مع r$1: المحتوى مخفي.",
index 75c41fc..a0fa693 100644 (file)
@@ -49,6 +49,8 @@
        "apihelp-block-param-reblock": "Skriv över befintlig blockering om användaren redan är blockerad.",
        "apihelp-block-param-watchuser": "Bevaka användarens eller IP-adressens användarsida och diskussionssida",
        "apihelp-block-param-tags": "Ändra märken att tillämpa i blockloggens post.",
+       "apihelp-block-param-pagerestrictions": "Lista över titlar att blockera användaren från att redigera. Gäller endast när <var>partial</var> är \"true\".",
+       "apihelp-block-param-namespacerestrictions": "Lista över namnrymds-ID:n att blockera användaren från att redigera. Gäller endast när <var>partial</var> är \"true\".",
        "apihelp-block-example-ip-simple": "Blockera IP-adressen <kbd>192.0.2.5</kbd> i tre dagar med motivationen <kbd>First strike</kbd>",
        "apihelp-block-example-user-complex": "Blockera användare <kbd>Vandal</kbd> på obegränsad tid med motivationen <kbd>Vandalism</kbd>, och förhindra kontoskapande och e-post.",
        "apihelp-changeauthenticationdata-summary": "Ändra autentiseringsdata för aktuell användare.",
        "apihelp-query+blocks-paramvalue-prop-expiry": "Lägger till en tidsstämpel för när blockeringen går ut.",
        "apihelp-query+blocks-paramvalue-prop-reason": "Lägger till de skäl som angetts för blockeringen.",
        "apihelp-query+blocks-paramvalue-prop-range": "Lägger till intervallet av IP-adresser som berörs av blockeringen.",
+       "apihelp-query+blocks-paramvalue-prop-restrictions": "Lägger till partiella blockeringsbegränsningar om blockeringen inte gäller för hela webbplatsen.",
        "apihelp-query+blocks-example-simple": "Lista blockeringar.",
        "apihelp-query+blocks-example-users": "Lista blockeringar av användarna <kbd>Alice</kbd> och <kbd>Bob</kbd>.",
        "apihelp-query+categories-summary": "Lista alla kategorier sidorna tillhör.",
index 83e8314..2a61360 100644 (file)
@@ -64,6 +64,9 @@
        "apihelp-block-param-reblock": "如果该用户已被封禁,则覆盖已有的封禁。",
        "apihelp-block-param-watchuser": "监视用户或该 IP 的用户页和讨论页。",
        "apihelp-block-param-tags": "要在封禁日志中应用到实体的更改标签。",
+       "apihelp-block-param-partial": "封禁用户于特定页面或名字空间而不是整个站点。",
+       "apihelp-block-param-pagerestrictions": "阻止用户编辑的标题列表。仅在<var>partial</var>设置为true时适用。",
+       "apihelp-block-param-namespacerestrictions": "用于阻止用户编辑的名字空间ID列表。仅在<var>partial</var>设置为true时适用。",
        "apihelp-block-example-ip-simple": "封禁IP地址<kbd>192.0.2.5</kbd>三天,原因<kbd>First strike</kbd>。",
        "apihelp-block-example-user-complex": "无限期封禁用户<kbd>Vandal</kbd>,原因<kbd>Vandalism</kbd>,并阻止新账户创建和电子邮件发送。",
        "apihelp-changeauthenticationdata-summary": "更改当前用户的身份验证数据。",
index b7c60ed..14a7717 100644 (file)
        "apiwarn-deprecation-missingparam": "因為未指定 <var>$1</var>,輸出內容使用到過去舊有的格式。該格式已棄用,並且往後都只會使用新格式。",
        "apiwarn-deprecation-parameter": "參數 <var>$1</var> 已棄用。",
        "apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> 自 MediaWiki 的 1.28 版本後已被棄用。當建立新 HTML 文件時請使用 <kbd>prop=headhtml</kbd>,或是當更新文件客戶端時請使用 <kbd>prop=modules|jsconfigvars</kbd>。",
+       "apiwarn-deprecation-post-without-content-type": "POST 請求不需要 <code>Content-Type</code> 標頭,這會無法可靠運作。",
        "apiwarn-deprecation-purge-get": "透過 GET 方式使用的 <kbd>action=purge</kbd> 已棄用,請以 POST 替代。",
        "apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> 已棄用,請改用 <kbd>$2</kbd>。",
        "apiwarn-difftohidden": "無法對 r$1 比較差異:內容被隱蔵。",
index 03043e1..948ed93 100644 (file)
@@ -104,6 +104,7 @@ class BlockManager {
         */
        public function getUserBlock( User $user, $fromReplica ) {
                $isAnon = $user->getId() === 0;
+               $fromMaster = !$fromReplica;
 
                // TODO: If $user is the current user, we should use the current request. Otherwise,
                // we should not look for XFF or cookie blocks.
@@ -127,7 +128,7 @@ class BlockManager {
                // User/IP blocking
                // After this, $blocks is an array of blocks or an empty array
                // TODO: remove dependency on DatabaseBlock
-               $blocks = DatabaseBlock::newListFromTarget( $user, $ip, !$fromReplica );
+               $blocks = DatabaseBlock::newListFromTarget( $user, $ip, $fromMaster );
 
                // Cookie blocking
                $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
@@ -164,7 +165,7 @@ class BlockManager {
                        $xff = array_map( 'trim', explode( ',', $xff ) );
                        $xff = array_diff( $xff, [ $ip ] );
                        // TODO: remove dependency on DatabaseBlock
-                       $xffblocks = DatabaseBlock::getBlocksForIPList( $xff, $isAnon, !$fromReplica );
+                       $xffblocks = DatabaseBlock::getBlocksForIPList( $xff, $isAnon, $fromMaster );
                        $blocks = array_merge( $blocks, $xffblocks );
                }
 
index fb4675e..ffc7cd0 100644 (file)
 
 use CLDRPluralRuleParser\Evaluator;
 use CLDRPluralRuleParser\Error as CLDRPluralRuleError;
-use MediaWiki\Config\ServiceOptions;
-use MediaWiki\Languages\LanguageNameUtils;
-use Psr\Log\LoggerInterface;
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Class for caching the contents of localisation files, Messages*.php
  * and *.i18n.php.
  *
- * An instance of this class is available using MediaWikiServices.
+ * An instance of this class is available using Language::getLocalisationCache().
  *
  * The values retrieved from here are merged, containing items from extension
  * files, core messages files and the language fallback sequence (e.g. zh-cn ->
@@ -41,8 +40,8 @@ use Psr\Log\LoggerInterface;
 class LocalisationCache {
        const VERSION = 4;
 
-       /** @var ServiceOptions */
-       private $options;
+       /** Configuration associative array */
+       private $conf;
 
        /**
         * True if recaching should only be done on an explicit call to recache().
@@ -51,6 +50,11 @@ class LocalisationCache {
         */
        private $manualRecache = false;
 
+       /**
+        * True to treat all files as expired until they are regenerated by this object.
+        */
+       private $forceRecache = false;
+
        /**
         * The cache data. 3-d array, where the first key is the language code,
         * the second key is the item key e.g. 'messages', and the third key is
@@ -67,16 +71,10 @@ class LocalisationCache {
        private $store;
 
        /**
-        * @var LoggerInterface
+        * @var \Psr\Log\LoggerInterface
         */
        private $logger;
 
-       /** @var callable[] See comment for parameter in constructor */
-       private $clearStoreCallbacks;
-
-       /** @var LanguageNameUtils */
-       private $langNameUtils;
-
        /**
         * A 2-d associative array, code/key, where presence indicates that the item
         * is loaded. Value arbitrary.
@@ -190,52 +188,60 @@ class LocalisationCache {
 
        private $mergeableKeys = null;
 
-       /**
-        * @todo Make this a const when HHVM support is dropped (T192166)
-        *
-        * @var array
-        * @since 1.34
-        */
-       public static $constructorOptions = [
-               // True to treat all files as expired until they are regenerated by this object.
-               'forceRecache',
-               'manualRecache',
-               'ExtensionMessagesFiles',
-               'MessagesDirs',
-       ];
-
        /**
         * For constructor parameters, see the documentation in DefaultSettings.php
         * for $wgLocalisationCacheConf.
         *
-        * Do not construct this directly. Use MediaWikiServices.
-        *
-        * @param ServiceOptions $options
-        * @param LCStore $store What backend to use for storage
-        * @param LoggerInterface $logger
-        * @param callable[] $clearStoreCallbacks To be called whenever the cache is cleared. Can be
-        *   used to clear other caches that depend on this one, such as ResourceLoader's
-        *   MessageBlobStore.
-        * @param LanguageNameUtils $langNameUtils
+        * @param array $conf
         * @throws MWException
         */
-       function __construct(
-               ServiceOptions $options,
-               LCStore $store,
-               LoggerInterface $logger,
-               array $clearStoreCallbacks,
-               LanguageNameUtils $langNameUtils
-       ) {
-               $options->assertRequiredOptions( self::$constructorOptions );
-
-               $this->options = $options;
-               $this->store = $store;
-               $this->logger = $logger;
-               $this->clearStoreCallbacks = $clearStoreCallbacks;
-               $this->langNameUtils = $langNameUtils;
-
-               // Keep this separate from $this->options so it can be mutable
-               $this->manualRecache = $options->get( 'manualRecache' );
+       function __construct( $conf ) {
+               global $wgCacheDirectory;
+
+               $this->conf = $conf;
+               $this->logger = LoggerFactory::getInstance( 'localisation' );
+
+               $directory = !empty( $conf['storeDirectory'] ) ? $conf['storeDirectory'] : $wgCacheDirectory;
+               $storeArg = [];
+               $storeArg['directory'] = $directory;
+
+               if ( !empty( $conf['storeClass'] ) ) {
+                       $storeClass = $conf['storeClass'];
+               } else {
+                       switch ( $conf['store'] ) {
+                               case 'files':
+                               case 'file':
+                                       $storeClass = LCStoreCDB::class;
+                                       break;
+                               case 'db':
+                                       $storeClass = LCStoreDB::class;
+                                       $storeArg['server'] = $conf['storeServer'] ?? [];
+                                       break;
+                               case 'array':
+                                       $storeClass = LCStoreStaticArray::class;
+                                       break;
+                               case 'detect':
+                                       if ( $directory ) {
+                                               $storeClass = LCStoreCDB::class;
+                                       } else {
+                                               $storeClass = LCStoreDB::class;
+                                               $storeArg['server'] = $conf['storeServer'] ?? [];
+                                       }
+                                       break;
+                               default:
+                                       throw new MWException(
+                                               'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
+                                       );
+                       }
+               }
+               $this->logger->debug( static::class . ": using store $storeClass" );
+
+               $this->store = new $storeClass( $storeArg );
+               foreach ( [ 'manualRecache', 'forceRecache' ] as $var ) {
+                       if ( isset( $conf[$var] ) ) {
+                               $this->$var = $conf[$var];
+                       }
+               }
        }
 
        /**
@@ -400,7 +406,7 @@ class LocalisationCache {
         * @return bool
         */
        public function isExpired( $code ) {
-               if ( $this->options->get( 'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
+               if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
                        $this->logger->debug( __METHOD__ . "($code): forced reload" );
 
                        return true;
@@ -445,7 +451,7 @@ class LocalisationCache {
                $this->initialisedLangs[$code] = true;
 
                # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
-               if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
+               if ( !Language::isValidBuiltInCode( $code ) ) {
                        $this->initShallowFallback( $code, 'en' );
 
                        return;
@@ -453,7 +459,7 @@ class LocalisationCache {
 
                # Recache the data if necessary
                if ( !$this->manualRecache && $this->isExpired( $code ) ) {
-                       if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
+                       if ( Language::isSupportedLanguage( $code ) ) {
                                $this->recache( $code );
                        } elseif ( $code === 'en' ) {
                                throw new MWException( 'MessagesEn.php is missing.' );
@@ -691,7 +697,7 @@ class LocalisationCache {
                global $IP;
 
                // This reads in the PHP i18n file with non-messages l10n data
-               $fileName = $this->langNameUtils->getMessagesFileName( $code );
+               $fileName = Language::getMessagesFileName( $code );
                if ( !file_exists( $fileName ) ) {
                        $data = [];
                } else {
@@ -798,12 +804,14 @@ class LocalisationCache {
        public function getMessagesDirs() {
                global $IP;
 
+               $config = MediaWikiServices::getInstance()->getMainConfig();
+               $messagesDirs = $config->get( 'MessagesDirs' );
                return [
                        'core' => "$IP/languages/i18n",
                        'exif' => "$IP/languages/i18n/exif",
                        'api' => "$IP/includes/api/i18n",
                        'oojs-ui' => "$IP/resources/lib/ooui/i18n",
-               ] + $this->options->get( 'MessagesDirs' );
+               ] + $messagesDirs;
        }
 
        /**
@@ -813,6 +821,8 @@ class LocalisationCache {
         * @throws MWException
         */
        public function recache( $code ) {
+               global $wgExtensionMessagesFiles;
+
                if ( !$code ) {
                        throw new MWException( "Invalid language code requested" );
                }
@@ -864,7 +874,7 @@ class LocalisationCache {
 
                # Load non-JSON localisation data for extensions
                $extensionData = array_fill_keys( $codeSequence, $initialData );
-               foreach ( $this->options->get( 'ExtensionMessagesFiles' ) as $extension => $fileName ) {
+               foreach ( $wgExtensionMessagesFiles as $extension => $fileName ) {
                        if ( isset( $messageDirs[$extension] ) ) {
                                # This extension has JSON message data; skip the PHP shim
                                continue;
@@ -1028,9 +1038,8 @@ class LocalisationCache {
                # HACK: If using a null (i.e. disabled) storage backend, we
                # can't write to the MessageBlobStore either
                if ( !$this->store instanceof LCStoreNull ) {
-                       foreach ( $this->clearStoreCallbacks as $callback ) {
-                               $callback();
-                       }
+                       $blobStore = MediaWikiServices::getInstance()->getResourceLoader()->getMessageBlobStore();
+                       $blobStore->clear();
                }
        }
 
@@ -1091,4 +1100,5 @@ class LocalisationCache {
                $this->store = new LCStoreNull;
                $this->manualRecache = false;
        }
+
 }
index db7141f..8b31f05 100644 (file)
@@ -186,7 +186,7 @@ class FileBackendGroup {
                                'mimeCallback' => [ $this, 'guessMimeInternal' ],
                                'obResetFunc' => 'wfResetOutputBuffers',
                                'streamMimeFunc' => [ StreamFile::class, 'contentTypeFromPath' ],
-                               'tmpFileFactory' => MediaWikiServices::getInstance()->getTempFSFileFactory(),
+                               'tmpFileFactory' => $services->getTempFSFileFactory(),
                                'statusWrapper' => [ Status::class, 'wrap' ],
                                'wanCache' => $services->getMainWANObjectCache(),
                                'srvCache' => ObjectCache::getLocalServerInstance( 'hash' ),
index 414222b..de15456 100644 (file)
@@ -412,17 +412,14 @@ abstract class Installer {
                // This will be overridden in the web installer with the user-specified language
                RequestContext::getMain()->setLanguage( 'en' );
 
+               // Disable the i18n cache
+               // TODO: manage LocalisationCache singleton in MediaWikiServices
+               Language::getLocalisationCache()->disableBackend();
+
                // Disable all global services, since we don't have any configuration yet!
                MediaWikiServices::disableStorageBackend();
 
                $mwServices = MediaWikiServices::getInstance();
-
-               // Disable i18n cache
-               $mwServices->getLocalisationCache()->disableBackend();
-
-               // Clear language cache so the old i18n cache doesn't sneak back in
-               Language::clearCaches();
-
                // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
                // SqlBagOStuff will then throw since we just disabled wfGetDB)
                $wgObjectCaches = $mwServices->getMainConfig()->get( 'ObjectCaches' );
index 7c39ded..01bb30e 100644 (file)
@@ -242,27 +242,6 @@ class SqliteInstaller extends DatabaseInstaller {
                $this->setVar( 'wgDBpassword', '' );
                $this->setupSchemaVars();
 
-               # Create the global cache DB
-               try {
-                       $conn = Database::factory(
-                               'sqlite', [ 'dbname' => 'wikicache', 'dbDirectory' => $dir ] );
-                       # @todo: don't duplicate objectcache definition, though it's very simple
-                       $sql =
-<<<EOT
-       CREATE TABLE IF NOT EXISTS objectcache (
-               keyname BLOB NOT NULL default '' PRIMARY KEY,
-               value BLOB,
-               exptime TEXT
-       )
-EOT;
-                       $conn->query( $sql );
-                       $conn->query( "CREATE INDEX IF NOT EXISTS exptime ON objectcache (exptime)" );
-                       $conn->query( "PRAGMA journal_mode=WAL" ); // this is permanent
-                       $conn->close();
-               } catch ( DBConnectionError $e ) {
-                       return Status::newFatal( 'config-sqlite-connection-error', $e->getMessage() );
-               }
-
                # Create the l10n cache DB
                try {
                        $conn = Database::factory(
index 42d7bf0..498fd7c 100644 (file)
        "config-install-step-failed": "mislykkedes",
        "config-install-extensions": "Inkluderer udvidelser",
        "config-install-database": "Opsætter database",
+       "config-install-user": "Opretter databasebruger",
        "config-install-user-alreadyexists": "Brugeren \"$1\" findes allerede",
        "config-install-user-create-failed": "Oprettelse af brugeren \"$1\" mislykkedes: $2",
        "config-install-tables": "Opretter tabeller",
        "config-install-keys": "Genererer hemmelige nøgler",
        "config-install-mainpage-exists": "Forsiden findes allerede, springer over",
        "config-install-mainpage-failed": "Kunne ikke indsætte forside: $1",
+       "config-install-db-success": "Databasen blev sat op",
        "config-help": "hjælp",
        "config-help-tooltip": "klik for at udvide",
        "config-nofile": "Filen \"$1\" kunne ikke blive fundet. Er den blevet slettet?",
index d0b43ad..a14bef1 100644 (file)
        "config-restart": "Ya, nyalakan ulang",
        "config-welcome": "=== Pengecekan lingkungan ===\nPengecekan dasar kini akan dilakukan untuk melihat apakah lingkungan ini memadai untuk instalasi MediaWiki.\nIngatlah untuk menyertakan informasi ini jika Anda mencari bantuan tentang cara menyelesaikan instalasi.",
        "config-welcome-section-copyright": "=== Hak cipta dan persyaratan ===\n\n$1\n\nProgram ini adalah perangkat lunak bebas; Anda dapat mendistribusikan dan/atau memodifikasinya di bawah persyaratan GNU General Public License seperti yang diterbitkan oleh Free Software Foundation; baik versi 2 lisensi, atau (sesuai pilihan Anda) versi yang lebih baru.\n\nProgram ini didistribusikan dengan harapan bahwa itu akan berguna, tetapi <strong>tanpa jaminan apa pun</strong>; bahkan tanpa jaminan tersirat untuk <strong>dapat diperjualbelikan</strong> atau <strong>sesuai untuk tujuan tertentu</strong>.\nLihat GNU General Public License untuk lebih jelasnya.\n\nAnda seharusnya telah menerima [$2 salinan dari GNU General Public License] bersama dengan program ini; jika tidak, kirimkan surat untuk Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, atau [https://www.gnu.org/copyleft/gpl.html baca versi daring].",
-       "config-sidebar": "* [https://www.mediawiki.org/wiki/MediaWiki/id Situs MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/id Pedoman Pengguna]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents/id Pedoman Administrator]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/id FAQ]\n----\n* <doclink href=Readme>Read me</doclink>\n* <doclink href=ReleaseNotes>Release notes</doclink>\n* <doclink href=Copying>Copying</doclink>\n* <doclink href=UpgradeDoc>Upgrading</doclink>",
+       "config-sidebar": "* [https://www.mediawiki.org Halaman depan MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Panduan Pengguna]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Panduan Pengurus]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Pertanyaan yang sering ditanyakan]",
+       "config-sidebar-readme": "Pelajari selengkapnya",
+       "config-sidebar-relnotes": "Catatan rilis",
+       "config-sidebar-license": "Menyalin",
+       "config-sidebar-upgrade": "Memperbarui",
        "config-env-good": "Kondisi telah diperiksa.\nAnda dapat menginstal MediaWiki.",
        "config-env-bad": "Kondisi telah diperiksa.\nAnda tidak dapat menginstal MediaWiki.",
        "config-env-php": "PHP $1 diinstal.",
        "config-env-hhvm": "HHVM $1 telah dipasang.",
-       "config-unicode-using-intl": "Menggunakan [https://pecl.php.net/intl ekstensi PECL intl] untuk normalisasi Unicode.",
+       "config-unicode-using-intl": "Menggunakan [https://php.net/manual/en/book.intl.php ekstensi internasional PHP] untuk normalisasi Unicode.",
        "config-unicode-pure-php-warning": "<strong>Peringatan:</strong> [https://pecl.php.net/intl intl Ekstensi PECL] tidak tersedia untuk menangani normalisasi Unicode, dikembalikan untuk melambatkan implementasi PHP asli.\nApabila Anda menjalankan situs dengan lalu-lintas tinggi, Anda harus membaca [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalisasi Unicode].",
        "config-unicode-update-warning": "<strong>Peringatan:</strong> Versi terinstal dari pembungkus normalisasi Unicode menggunakan versi lama pustaka [http://site.icu-project.org/ proyek ICU].\nAnda harus [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations meningkatkan versinya] jika ingin menggunakan Unicode.",
        "config-no-db": "Pengandar basis data yang sesuai tidak ditemukan! Anda perlu menginstal pengandar basis data untuk PHP.\n{{PLURAL:$2|Jenis|Jenis}} basis data yang didukung: $1.\n\nJika Anda mengompilasi PHP sendiri, ubahlah konfigurasinya dengan mengaktifkan klien basis data, misalnya menggunakan <code>./configure --with-mysqli</code>.\nJika Anda menginstal PHP dari paket Debian atau Ubuntu, maka Anda juga perlu menginstal seperti paket <code>php-mysql</code>.",
@@ -95,7 +99,7 @@
        "config-db-host": "Inang basis data:",
        "config-db-host-help": "Jika server basis data Anda berada di server yang berbeda, masukkan nama inang atau alamat IP di sini.\n\nJika Anda menggunakan inang web bersama, penyedia inang Anda harus memberikan nama inang yang benar di dokumentasi mereka.\n\nJika Anda menginstal pada server Windows dan menggunakan MySQL, \"localhost\" mungkin tidak dapat digunakan sebagai nama server. Jika demikian, coba \"127.0.0.1\" untuk alamat IP lokal.\n\nJika Anda menggunakan PostgreSQL, biarkan field ini kosong untuk menghubungkan lewat soket Unix.",
        "config-db-wiki-settings": "Identifikasi wiki ini",
-       "config-db-name": "Nama basis data:",
+       "config-db-name": "Nama basis data (tanpa tanda hubung):",
        "config-db-name-help": "Pilih nama yang mengidentifikasikan wiki Anda.\nNama tersebut tidak boleh mengandung spasi.\n\nJika Anda menggunakan inang web bersama, penyedia inang Anda dapat memberikan Anda nama basis data khusus untuk digunakan atau mengizinkan Anda membuat basis data melalui panel kontrol.",
        "config-db-install-account": "Akun pengguna untuk instalasi",
        "config-db-username": "Nama pengguna basis data:",
        "config-db-account-lock": "Gunakan nama pengguna dan kata sandi yang sama selama operasi normal",
        "config-db-wiki-account": "Akun pengguna untuk operasi normal",
        "config-db-wiki-help": "Masukkan nama pengguna dan sandi yang akan digunakan untuk terhubung ke basis data wiki selama operasi normal.\nJika akun tidak ada, akun instalasi memiliki hak yang memadai, akun pengguna ini akan dibuat dengan hak akses minimum yang diperlukan untuk mengoperasikan wiki.",
-       "config-db-prefix": "Prefiks tabel basis data:",
+       "config-db-prefix": "Prefiks tabel basis data (tanpa tanda hubung):",
        "config-db-prefix-help": "Jika Anda perlu berbagi satu basis data di antara beberapa wiki, atau antara MediaWiki dan aplikasi web lain, Anda dapat memilih untuk menambahkan prefiks terhadap semua nama tabel demi menghindari konflik.\nJangan gunakan spasi.\n\nPrefiks ini biasanya dibiarkan kosong.",
        "config-mysql-old": "MySQL $1 atau versi terbaru diperlukan, Anda menggunakan $2.",
        "config-db-port": "Porta basis data:",
-       "config-db-schema": "Skema untuk MediaWiki",
+       "config-db-schema": "Skema untuk MediaWiki (tanpa tanda hubung):",
        "config-db-schema-help": "Skema ini biasanya berjalan baik.\nUbah hanya jika Anda tahu Anda perlu mengubahnya.",
        "config-pg-test-error": "Tidak dapat terhubung ke basis data <strong>$1</strong>: $2",
        "config-sqlite-dir": "Direktori data SQLite:",
        "config-sqlite-dir-help": "SQLite menyimpan semua data dalam satu berkas.\n\nDirektori yang Anda berikan harus dapat ditulisi oleh server web selama instalasi.\n\nDirektori itu '''tidak''' boleh dapat diakses melalui web, inilah sebabnya kami tidak menempatkannya bersama dengan berkas PHP lain.\n\nPenginstal akan membuat berkas <code>.htaccess</code> bersamaan dengan itu, tetapi jika gagal, orang dapat memperoleh akses ke basis data mentah Anda.\nItu termasuk data mentah pengguna (alamat surel, hash sandi) serta revisi yang dihapus dan data lainnya yang dibatasi pada wiki.\n\nPertimbangkan untuk menempatkan basis data di tempat lain, misalnya di <code>/var/lib/mediawiki/yourwiki</code>.",
-       "config-type-mysql": "MySQL (atau yang kompatibel)",
+       "config-type-mysql": "MariaDB, MySQL, atau yang kompatibel",
        "config-type-postgres": "PostgreSQL",
        "config-type-sqlite": "SQLite",
        "config-support-info": "MediaWiki mendukung sistem basis data berikut:\n\n$1\n\nJika Anda tidak melihat sistem basis data yang Anda gunakan tercantum di bawah ini, ikuti petunjuk terkait di atas untuk mengaktifkan dukungan.",
        "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] adalah target utama MediaWiki dan memiliki dukungan terbaik. MediaWiki juga berjalan dengan [{{int:version-db-mariadb-url}} MariaDB] dan [{{int:version-db-percona-url}} Server Percona], yang kompatibel dengan MySQL. ([https://www.php.net/manual/en/mysql.installation.php Cara mengompilasi PHP dengan dukungan MySQL])",
        "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] adalah sistem basis data sumber terbuka populer sebagai alternatif MySQL.([https://www.php.net/manual/en/pgsql.installation.php Bagaimana mengompilasikan PHP dengan dukungan PostgreSQL])",
-       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] adalah sistem basis data yang ringan yang sangat baik dukungannya. ([http://www.php.net/manual/en/pdo.installation.php cara mengompilasi PHP dengan dukungan SQLite], menggunakan PDO)",
+       "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] adalah sistem basis data yang ringan yang sangat baik dukungannya. ([http://www.php.net/manual/en/pdo.installation.php Bagaimana mengompilasi PHP dengan dukungan SQLite], menggunakan PDO)",
        "config-header-mysql": "Pengaturan MariaDB/MySQL",
        "config-header-postgres": "Pengaturan PostgreSQL",
        "config-header-sqlite": "Pengaturan SQLite",
        "config-missing-db-host": "Anda harus memasukkan nilai untuk \"{{int:config-db-host}}\"",
        "config-invalid-db-name": "Nama basis data \"$1\" tidak sah.\nGunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan tanda hubung (-).",
        "config-invalid-db-prefix": "Prefiks basis data \"$1\" tidak sah.\nGunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan tanda hubung (-).",
-       "config-connection-error": "$1.\n\nPeriksa nama inang, pengguna, dan sandi di bawah ini dan coba lagi.",
+       "config-connection-error": "$1.\n\nPeriksa nama inang, pengguna, dan kata sandi dan coba lagi. Jika menggunakan \"localhost\" sebagai inang basis data, coba gunakan \"127.0.0.1\" (atau sebaliknya).",
        "config-invalid-schema": "Skema MediaWiki \"$1\" tidak sah.\nGunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), dan garis bawah (_).",
        "config-postgres-old": "PostgreSQL $1 atau versi terbaru diperlukan, Anda menggunakan $2.",
        "config-sqlite-name-help": "Pilih nama yang mengidentifikasi wiki Anda.\nJangan gunakan spasi atau tanda hubung.\nNama ini akan digunakan untuk nama berkas data SQLite.",
        "config-sqlite-cant-create-db": "Tidak dapat membuat berkas basis data <code>$1</code>.",
        "config-sqlite-fts3-downgrade": "PHP tidak memiliki dukungan FTS3, tabel dituruntarafkan.",
        "config-can-upgrade": "Ada tabel MediaWiki di basis dataini.\nUntuk memperbaruinya ke MediaWiki $1, klik '''Lanjut'''.",
+       "config-upgrade-error": "Terjadi sebuah galat ketika memperbarui tabel MediaWiki dalam basis data Anda.\n\nUntuk informasi lebih lanjut, lihat catatan di atas, untuk mencoba kembali klik <strong>Lanjutkan</strong>.",
        "config-upgrade-done": "Pemutakhiran selesai.\n\nAnda sekarang dapat [$1 mulai menggunakan wiki Anda].\n\nJika Anda ingin membuat ulang berkas <code>LocalSettings.php</code>, klik tombol di bawah ini.\nTindakan ini '''tidak dianjurkan''' kecuali jika Anda mengalami masalah dengan wiki Anda.",
        "config-upgrade-done-no-regenerate": "Pemutakhiran selesai.\n\nAnda sekarang dapat [$1 mulai menggunakan wiki Anda].",
        "config-regenerate": "Regenerasi LocalSettings.php →",
        "config-db-web-create": "Buat akun jika belum ada",
        "config-db-web-no-create-privs": "Akun Anda berikan untuk instalasi tidak memiliki hak yang cukup untuk membuat akun.\nAkun yang Anda berikan harus sudah ada.",
        "config-mysql-engine": "Mesin penyimpanan:",
-       "config-mysql-innodb": "InnoDB",
+       "config-mysql-innodb": "InnoDB (disarankan)",
        "config-mysql-engine-help": "'''InnoDB''' hampir selalu merupakan pilihan terbaik karena memiliki dukungan konkurensi yang baik.\n\n'''MyISAM''' mungkin lebih cepat dalam instalasi pengguna-tunggal atau hanya-baca.\nBasis data MyISAM cenderung lebih sering rusak daripada basis data InnoDB.",
        "config-site-name": "Nama wiki:",
        "config-site-name-help": "Ini akan muncul di bilah judul peramban dan di berbagai tempat lainnya.",
        "config-install-subscribe-fail": "Tidak dapat berlangganan mediawiki-announce: $1",
        "config-install-subscribe-notpossible": "cURL tidak diinstal dan <code>allow_url_fopen</code> tidak tersedia.",
        "config-install-mainpage": "Membuat halaman utama dengan konten bawaan",
+       "config-install-mainpage-exists": "Halaman utama sudah ada, meloncati",
        "config-install-extension-tables": "Pembuatan tabel untuk ekstensi yang diaktifkan",
        "config-install-mainpage-failed": "Tidak dapat membuat halaman utama: $1",
        "config-install-done": "<strong>Selamat!</strong>\nAnda telah berhasil menginstal MediaWiki.\n\nPemasang telah membuat sebuah berkas <code>LocalSettings.php</code>.\nBerkas itu berisi semua setelan Anda.\n\nAnda perlu mengunduh berkas itu dan meletakkannya di direktori instalasi wiki (direktori yang sama dengan index.php). Pengunduhan akan dimulai secara otomatis.\n\nJika pengunduhan tidak terjadi, atau jika Anda membatalkannya, Anda dapat mengulangi pengunduhan dengan mengeklik tautan berikut:\n\n$3\n\n<strong>Catatan</strong>: Jika Anda tidak melakukannya sekarang, berkas konfigurasi yang dihasilkan ini tidak akan tersedia lagi setelah Anda keluar dari proses instalasi tanpa mengunduhnya.\n\nSetelah melakukannya, Anda dapat <strong>[$2 memasuki wiki Anda]</strong>.",
+       "config-install-success": "MediaWiki telah dipasang dengan sukses. Anda dapat mengunjungi <$1$2> untuk melihat wiki ini. Jika Anda memiliki pertanyaan, lihat daftar pertanyaan yang sering ditanyakan: <https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ> atau gunakan salah satu forum yang ada di halaman tersebut.",
+       "config-install-db-success": "Basis data telah sukses diatur",
        "config-download-localsettings": "Unduh <code>LocalSettings.php</code>",
        "config-help": "bantuan",
        "config-help-tooltip": "klik untuk memperluas",
        "config-skins-screenshots": "$1 (tangkapan layar: $2)",
        "config-extensions-requires": "$1 (memerlukan $2)",
        "config-screenshot": "tangkapan layar",
+       "config-extension-not-found": "Tidak dapat menemukan berkas registrasi untuk ekstensi \"$1\"",
        "mainpagetext": "<strong>MediaWiki telah terpasang dengan sukses.</strong>",
        "mainpagedocfooter": "Konsultasikan [https://www.mediawiki.org/wiki/Help:Contents Panduan Pengguna] untuk cara penggunaan perangkat lunak wiki ini.\n\n== Memulai ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Daftar pengaturan konfigurasi]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Pertanyaan yang sering diajukan mengenai MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Pelokalan MediaWiki untuk bahasa Anda]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Belajar bagaimana menghadapi spam di wiki lokal]"
 }
index a6e9ddb..3d2311e 100644 (file)
        "config-welcome": "=== Verificări ale mediului ===\nVerificări de bază vor fi efectuate pentru a vedea dacă este potrivit pentru instalarea MediaWiki.\nNu uitați să includeți aceste informații dacă doriți asistență pentru completarea instalării.",
        "config-welcome-section-copyright": "=== Drepturi de autor și termeni ===\n\n$1\n\nAcest program este un software liber; îl puteți redistribui și / sau modifica în conformitate cu termenii Licenței Publice Generale GNU, publicată de Fundația pentru Software Liber; fie versiunea 2 a Licenței, fie (la alegere) orice versiune ulterioară.\nAcest program este distribuit în speranța că va fi util, dar <strong>fără nicio garanție</strong>; fără nici măcar garanția implicită de <strong>vandabilitate</strong> sau <strong>fitness pentru un anumit scop</strong>.\nPentru mai multe detalii, consultați Licența publică generală GNU.\nAr fi trebuit să fi primit [$2 o copie a GNU General Public License] împreună cu acest program; dacă nu, scrieți la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, SUA, sau [https://www.gnu.org/copyleft/gpl.html citiți-o online] .",
        "config-sidebar": "* [https://www.mediawiki.org Acasă MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User's Guide]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Administrator's Guide]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Read me</doclink>\n* <doclink href=ReleaseNotes>Release notes</doclink>\n* <doclink href=Copying>Copying</doclink>\n* <doclink href=UpgradeDoc>Upgrading</doclink>",
+       "config-sidebar-readme": "Read me",
+       "config-sidebar-relnotes": "Note de lansare",
+       "config-sidebar-license": "Copiere",
+       "config-sidebar-upgrade": "Actualizare",
        "config-env-good": "Verificarea mediului a fost efectuată cu succes.\nPuteți instala MediaWiki.",
        "config-env-bad": "Verificarea mediului a fost efectuată.\nNu puteți instala MediaWiki.",
        "config-env-php": "PHP $1 este instalat.",
        "config-env-hhvm": "HHVM $1 este instalat.",
-       "config-unicode-using-intl": "Utilizarea extensiei [https://pecl.php.net/intl intl PECL] pentru normalizarea Unicode.",
-       "config-unicode-pure-php-warning": "<strong>Atenție:</strong> Extensia [https://pecl.php.net/intl intl PECL] nu este disponibilă pentru a face față normalizării Unicode, revenind la o implementare lentă pur PHP.\nDacă rulați un site cu trafic ridicat, ar trebui să citiți puțin în [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Normalizarea Unicode].",
+       "config-unicode-using-intl": "Se folosește extensia [https://php.net/manual/en/book.intl.php PHP intl] pentru normalizarea Unicode.",
+       "config-unicode-pure-php-warning": "<strong>Atenție:</strong> Extensia [https://php.net/manual/en/book.intl.php PHP intl] nu este disponibilă pentru a procesa normalizarea Unicode, se folosește o implementare lentă pur PHP.\nDacă rulați un site cu trafic ridicat, ar trebui să citiți despre [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Normalizarea Unicode].",
        "config-unicode-update-warning": "<strong>Avertisment:</strong> Versiunea instalată a pachetului de normalizare Unicode utilizează o versiune mai veche a bibliotecii [http://site.icu-project.org/ proiectul ICU].\nAr trebui să faceți upgrade [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations] dacă sunteți preocupat de utilizarea Unicode.",
        "config-no-db": "Nu am găsit un driver de bază de date potrivit! Trebuie să instalați un driver de bază de date pentru PHP.\nUrmătoarea bază de date {{PLURAL:$2|tip este|tipuri sunt}} este acceptată: $1.\nDacă ați compilat singuri PHP, reconfigurați-l cu un client de bază de date activat, de exemplu, utilizând <code>./ configure --with-mysqli</code>.\nDacă ați instalat PHP dintr-un pachet Debian sau Ubuntu, atunci trebuie să instalați, de exemplu, pachetul <code>php-mysql</code>",
-       "config-outdated-sqlite": "<strong>Atenție:</strong> ai SQLite $1, care este mai mic decât minimul necesar pentru versiunea $2. SQLite va fi nedisponibil.",
+       "config-outdated-sqlite": "<strong>Atenție:</strong> aveții SQLite $2, care este mai mic decât versiunea minimă $1. SQLite nu va fi disponibil.",
        "config-no-fts3": "<strong>Atenție:</strong> SQLite este compus fără [//sqlite.org/fts3.html modulu FTS3], caută caracteristici care nu vor fi disponibile la finalul acesta.",
        "config-pcre-old": "<strong>Fatal:</> PCRE $1 sau mai târziu este necesar este necesar. \nPHP tău este binar este legat de PCRE $2. \n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Mai multe informații].",
        "config-pcre-no-utf8": "<strong>Fatal:</strong> Modul PCRE al PHP pare să fie compilat fără suport PCRE_UTF8.\nMediaWiki necesită ca suportul UTF-8 să funcționeze corect.",
        "config-admin-name": "Numele dumneavoastră de utilizator:",
        "config-admin-password": "Parolă:",
        "config-admin-password-confirm": "Parola, din nou:",
+       "config-admin-name-blank": "Introduceți numele de utilizator al administratorului.",
        "config-admin-password-blank": "Introduceți o parolă pentru contul de administrator.",
        "config-admin-password-mismatch": "Cele două parole introduse nu corespund.",
        "config-admin-email": "Adresa de e-mail:",
        "config-license-pd": "Domeniu public",
        "config-license-cc-choose": "Alegeți o licență Creative Commons personalizată",
        "config-email-settings": "Setări pentru e-mail",
+       "config-enable-email": "Permiteți trimiterea de e-mail",
+       "config-email-user": "Permiteți e-mailurile între utilizatori",
        "config-email-usertalk": "Activați notificările pentru pagina de discuții a utilizatorului",
        "config-upload-settings": "Încărcare de imagini și fișiere",
        "config-upload-deleted": "Director pentru fișierele șterse:",
index 9bab12f..3ea2df6 100644 (file)
        "config-memcached-servers": "Memcached-serveri:",
        "config-memcached-help": "Lista IP adresa za uporabu u Memcached.\nTreba da se navede jednu u svaki red, kao i port što će se koristiti. Na primer:\n 127.0.0.1:11211\n 192.168.1.25:1234",
        "config-memcache-needservers": "Odabrali ste Memcached kao vaš tip međuspremnika (keša), ali niste naveli nijedan server.",
-       "mainpagetext": "<strong>MediaWiki je uspješno instaliran.</strong>",
+       "config-install-step-done": "gotovo",
+       "config-install-step-failed": "nije uspjelo",
+       "config-install-extensions": "Uključujem dodatke",
+       "config-install-database": "Postavljam bazu podataka",
+       "config-install-schema": "Pravim šemu",
+       "config-install-pg-schema-not-exist": "PostgreSQL-šema ne postoji.",
+       "config-install-pg-schema-failed": "Pravljenje natabela nije uspelo.\nUvjerite se da korisnik „$1” može da zapisuje u šemi „$2”.",
+       "config-install-pg-commit": "Usproveđivanje promjena",
+       "config-install-user": "Pravim korisnika baze podataka",
+       "config-install-user-alreadyexists": "Korisnik \"$1\" već postoji",
+       "config-install-user-create-failed": "Pravljenje korisnika \"$1\" nije uspjelo: $2",
+       "config-install-user-grant-failed": "Dodjeljivanje dozvola korisniku \"$1\" nije uspjelo: $2",
+       "config-install-user-missing": "Navedeni korisnik \"$1\" ne postoji.",
+       "config-install-user-missing-create": "Navedeni korisnik \"$1\" ne postoji.\nAko želite da ga otvorite, štiklirajte mogućnost „napravi račun”.",
+       "config-install-tables": "Pravim tabele",
+       "config-install-tables-exist": "<strong>Upozorenje:</strong> Izgleda da MediaWiki tabele već postoje.\nPreskočim pravljenje.",
+       "config-install-tables-failed": "<strong>Greška:</strong> Pravljenje tabele nije uspjelo zbog sljedeće greške: $1",
+       "config-install-interwiki": "Popunjavam predodređene međuprojektne tabele",
+       "config-install-interwiki-list": "Nisam mogao pronaći datoteku <code>interwiki.list</code>.",
+       "config-install-interwiki-exists": "<strong>Upozorenje:</strong> Tabela međuwikija već ima unose.\nPreskočim podrazumevano-zadanu listu.",
+       "config-install-stats": "Pokrećem statistiku",
+       "config-install-keys": "Generisanje tajnih ključeva",
+       "config-install-updates": "Spriječi vršenje nepotrebnih podnova",
+       "config-install-updates-failed": "<strong>Greška:</strong> Umetanje podnovnih klučeva u tabele nije uspjelo, zbog sljedeće greške: $1",
+       "config-install-sysop": "Otvaranje korisničkog računa administratora",
+       "config-install-subscribe-fail": "Nije moguće Vas pretplatiti se na izvješćenje mediawiki-announce: $1",
+       "config-install-subscribe-notpossible": "cURL nije instaliran, a <code>allow_url_fopen</code> nije dostupno.",
+       "config-install-mainpage": "Pravim početnu stranicu sa standardnim sadržajem",
+       "config-install-mainpage-exists": "Početna strana već postoji. Prelazim na sljedeće.",
+       "config-install-extension-tables": "Izrada tabela za omogućene dodatke",
+       "config-install-mainpage-failed": "Nisam mogao umetnuti početnu stranu: $1",
+       "config-download-localsettings": "Preuzmi <code>LocalSettings.php</code>",
+       "config-help": "pomoć",
+       "config-help-tooltip": "kliknite da rasklopite",
+       "config-nofile": "Datoteka \"$1\" nije pronađena. Da nije obrisana?",
+       "config-skins-screenshots": "$1 (ekr. snimci: $2)",
+       "config-extensions-requires": "$1 (zahtjeva $2)",
+       "config-screenshot": "ekranski snimak",
+       "config-extension-not-found": "Nisam mogao naći datoteku registracije za dodatak „$1”",
+       "config-extension-dependency": "Naišao na grešku sa zavisnošću pri instaliranju dodatka „$1”: $2",
+       "mainpagetext": "<strong>MediaWiki je instaliran.</strong>",
        "mainpagedocfooter": "Za informacije o korištenju wiki softvera konzultirajte [https://meta.wikimedia.org/wiki/Help:Contents Vodič za korisnike].\n\n== Uvod u rad ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista konfiguracije postavki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista primatelja izdanja MediaWikija]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Lokalizirajte MediaWiki za svoj jezik]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Saznajte kako se boriti protiv spama na svojem wikiju]"
 }
diff --git a/includes/language/ConverterRule.php b/includes/language/ConverterRule.php
new file mode 100644 (file)
index 0000000..4a330ad
--- /dev/null
@@ -0,0 +1,498 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Language
+ */
+
+/**
+ * Parser for rules of language conversion, parse rules in -{ }- tag.
+ * @ingroup Language
+ * @author fdcn <fdcn64@gmail.com>, PhiLiP <philip.npc@gmail.com>
+ */
+class ConverterRule {
+       public $mText; // original text in -{text}-
+       public $mConverter; // LanguageConverter object
+       public $mRuleDisplay = '';
+       public $mRuleTitle = false;
+       public $mRules = ''; // string : the text of the rules
+       public $mRulesAction = 'none';
+       public $mFlags = [];
+       public $mVariantFlags = [];
+       public $mConvTable = [];
+       public $mBidtable = []; // array of the translation in each variant
+       public $mUnidtable = []; // array of the translation in each variant
+
+       /**
+        * @param string $text The text between -{ and }-
+        * @param LanguageConverter $converter
+        */
+       public function __construct( $text, $converter ) {
+               $this->mText = $text;
+               $this->mConverter = $converter;
+       }
+
+       /**
+        * Check if variants array in convert array.
+        *
+        * @param array|string $variants Variant language code
+        * @return string Translated text
+        */
+       public function getTextInBidtable( $variants ) {
+               $variants = (array)$variants;
+               if ( !$variants ) {
+                       return false;
+               }
+               foreach ( $variants as $variant ) {
+                       if ( isset( $this->mBidtable[$variant] ) ) {
+                               return $this->mBidtable[$variant];
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Parse flags with syntax -{FLAG| ... }-
+        * @private
+        */
+       function parseFlags() {
+               $text = $this->mText;
+               $flags = [];
+               $variantFlags = [];
+
+               $sepPos = strpos( $text, '|' );
+               if ( $sepPos !== false ) {
+                       $validFlags = $this->mConverter->mFlags;
+                       $f = StringUtils::explode( ';', substr( $text, 0, $sepPos ) );
+                       foreach ( $f as $ff ) {
+                               $ff = trim( $ff );
+                               if ( isset( $validFlags[$ff] ) ) {
+                                       $flags[$validFlags[$ff]] = true;
+                               }
+                       }
+                       $text = strval( substr( $text, $sepPos + 1 ) );
+               }
+
+               if ( !$flags ) {
+                       $flags['S'] = true;
+               } elseif ( isset( $flags['R'] ) ) {
+                       $flags = [ 'R' => true ];// remove other flags
+               } elseif ( isset( $flags['N'] ) ) {
+                       $flags = [ 'N' => true ];// remove other flags
+               } elseif ( isset( $flags['-'] ) ) {
+                       $flags = [ '-' => true ];// remove other flags
+               } elseif ( count( $flags ) == 1 && isset( $flags['T'] ) ) {
+                       $flags['H'] = true;
+               } elseif ( isset( $flags['H'] ) ) {
+                       // replace A flag, and remove other flags except T
+                       $temp = [ '+' => true, 'H' => true ];
+                       if ( isset( $flags['T'] ) ) {
+                               $temp['T'] = true;
+                       }
+                       if ( isset( $flags['D'] ) ) {
+                               $temp['D'] = true;
+                       }
+                       $flags = $temp;
+               } else {
+                       if ( isset( $flags['A'] ) ) {
+                               $flags['+'] = true;
+                               $flags['S'] = true;
+                       }
+                       if ( isset( $flags['D'] ) ) {
+                               unset( $flags['S'] );
+                       }
+                       // try to find flags like "zh-hans", "zh-hant"
+                       // allow syntaxes like "-{zh-hans;zh-hant|XXXX}-"
+                       $variantFlags = array_intersect( array_keys( $flags ), $this->mConverter->mVariants );
+                       if ( $variantFlags ) {
+                               $variantFlags = array_flip( $variantFlags );
+                               $flags = [];
+                       }
+               }
+               $this->mVariantFlags = $variantFlags;
+               $this->mRules = $text;
+               $this->mFlags = $flags;
+       }
+
+       /**
+        * Generate conversion table.
+        * @private
+        */
+       function parseRules() {
+               $rules = $this->mRules;
+               $bidtable = [];
+               $unidtable = [];
+               $variants = $this->mConverter->mVariants;
+               $varsep_pattern = $this->mConverter->getVarSeparatorPattern();
+
+               // Split according to $varsep_pattern, but ignore semicolons from HTML entities
+               $rules = preg_replace( '/(&[#a-zA-Z0-9]+);/', "$1\x01", $rules );
+               $choice = preg_split( $varsep_pattern, $rules );
+               $choice = str_replace( "\x01", ';', $choice );
+
+               foreach ( $choice as $c ) {
+                       $v = explode( ':', $c, 2 );
+                       if ( count( $v ) != 2 ) {
+                               // syntax error, skip
+                               continue;
+                       }
+                       $to = trim( $v[1] );
+                       $v = trim( $v[0] );
+                       $u = explode( '=>', $v, 2 );
+                       $vv = $this->mConverter->validateVariant( $v );
+                       // if $to is empty (which is also used as $from in bidtable),
+                       // strtr() could return a wrong result.
+                       if ( count( $u ) == 1 && $to !== '' && $vv ) {
+                               $bidtable[$vv] = $to;
+                       } elseif ( count( $u ) == 2 ) {
+                               $from = trim( $u[0] );
+                               $v = trim( $u[1] );
+                               $vv = $this->mConverter->validateVariant( $v );
+                               // if $from is empty, strtr() could return a wrong result.
+                               if ( array_key_exists( $vv, $unidtable )
+                                       && !is_array( $unidtable[$vv] )
+                                       && $from !== ''
+                                       && $vv ) {
+                                       $unidtable[$vv] = [ $from => $to ];
+                               } elseif ( $from !== '' && $vv ) {
+                                       $unidtable[$vv][$from] = $to;
+                               }
+                       }
+                       // syntax error, pass
+                       if ( !isset( $this->mConverter->mVariantNames[$vv] ) ) {
+                               $bidtable = [];
+                               $unidtable = [];
+                               break;
+                       }
+               }
+               $this->mBidtable = $bidtable;
+               $this->mUnidtable = $unidtable;
+       }
+
+       /**
+        * @private
+        *
+        * @return string
+        */
+       function getRulesDesc() {
+               $codesep = $this->mConverter->mDescCodeSep;
+               $varsep = $this->mConverter->mDescVarSep;
+               $text = '';
+               foreach ( $this->mBidtable as $k => $v ) {
+                       $text .= $this->mConverter->mVariantNames[$k] . "$codesep$v$varsep";
+               }
+               foreach ( $this->mUnidtable as $k => $a ) {
+                       foreach ( $a as $from => $to ) {
+                               $text .= $from . '⇒' . $this->mConverter->mVariantNames[$k] .
+                                       "$codesep$to$varsep";
+                       }
+               }
+               return $text;
+       }
+
+       /**
+        * Parse rules conversion.
+        * @private
+        *
+        * @param string $variant
+        *
+        * @return string
+        */
+       function getRuleConvertedStr( $variant ) {
+               $bidtable = $this->mBidtable;
+               $unidtable = $this->mUnidtable;
+
+               if ( count( $bidtable ) + count( $unidtable ) == 0 ) {
+                       return $this->mRules;
+               } else {
+                       // display current variant in bidirectional array
+                       $disp = $this->getTextInBidtable( $variant );
+                       // or display current variant in fallbacks
+                       if ( $disp === false ) {
+                               $disp = $this->getTextInBidtable(
+                                       $this->mConverter->getVariantFallbacks( $variant ) );
+                       }
+                       // or display current variant in unidirectional array
+                       if ( $disp === false && array_key_exists( $variant, $unidtable ) ) {
+                               $disp = array_values( $unidtable[$variant] )[0];
+                       }
+                       // or display first text under disable manual convert
+                       if ( $disp === false && $this->mConverter->mManualLevel[$variant] == 'disable' ) {
+                               if ( count( $bidtable ) > 0 ) {
+                                       $disp = array_values( $bidtable )[0];
+                               } else {
+                                       $disp = array_values( array_values( $unidtable )[0] )[0];
+                               }
+                       }
+                       return $disp;
+               }
+       }
+
+       /**
+        * Similar to getRuleConvertedStr(), but this prefers to use original
+        * page title if $variant === $this->mConverter->mMainLanguageCode
+        * and may return false in this case (so this title conversion rule
+        * will be ignored and the original title is shown).
+        *
+        * @since 1.22
+        * @param string $variant The variant code to display page title in
+        * @return string|bool The converted title or false if just page name
+        */
+       function getRuleConvertedTitle( $variant ) {
+               if ( $variant === $this->mConverter->mMainLanguageCode ) {
+                       // If a string targeting exactly this variant is set,
+                       // use it. Otherwise, just return false, so the real
+                       // page name can be shown (and because variant === main,
+                       // there'll be no further automatic conversion).
+                       $disp = $this->getTextInBidtable( $variant );
+                       if ( $disp ) {
+                               return $disp;
+                       }
+                       if ( array_key_exists( $variant, $this->mUnidtable ) ) {
+                               $disp = array_values( $this->mUnidtable[$variant] )[0];
+                       }
+                       // Assigned above or still false.
+                       return $disp;
+               } else {
+                       return $this->getRuleConvertedStr( $variant );
+               }
+       }
+
+       /**
+        * Generate conversion table for all text.
+        * @private
+        */
+       function generateConvTable() {
+               // Special case optimisation
+               if ( !$this->mBidtable && !$this->mUnidtable ) {
+                       $this->mConvTable = [];
+                       return;
+               }
+
+               $bidtable = $this->mBidtable;
+               $unidtable = $this->mUnidtable;
+               $manLevel = $this->mConverter->mManualLevel;
+
+               $vmarked = [];
+               foreach ( $this->mConverter->mVariants as $v ) {
+                       /* for bidirectional array
+                               fill in the missing variants, if any,
+                               with fallbacks */
+                       if ( !isset( $bidtable[$v] ) ) {
+                               $variantFallbacks =
+                                       $this->mConverter->getVariantFallbacks( $v );
+                               $vf = $this->getTextInBidtable( $variantFallbacks );
+                               if ( $vf ) {
+                                       $bidtable[$v] = $vf;
+                               }
+                       }
+
+                       if ( isset( $bidtable[$v] ) ) {
+                               foreach ( $vmarked as $vo ) {
+                                       // use syntax: -{A|zh:WordZh;zh-tw:WordTw}-
+                                       // or -{H|zh:WordZh;zh-tw:WordTw}-
+                                       // or -{-|zh:WordZh;zh-tw:WordTw}-
+                                       // to introduce a custom mapping between
+                                       // words WordZh and WordTw in the whole text
+                                       if ( $manLevel[$v] == 'bidirectional' ) {
+                                               $this->mConvTable[$v][$bidtable[$vo]] = $bidtable[$v];
+                                       }
+                                       if ( $manLevel[$vo] == 'bidirectional' ) {
+                                               $this->mConvTable[$vo][$bidtable[$v]] = $bidtable[$vo];
+                                       }
+                               }
+                               $vmarked[] = $v;
+                       }
+                       /* for unidirectional array fill to convert tables */
+                       if ( ( $manLevel[$v] == 'bidirectional' || $manLevel[$v] == 'unidirectional' )
+                               && isset( $unidtable[$v] )
+                       ) {
+                               if ( isset( $this->mConvTable[$v] ) ) {
+                                       $this->mConvTable[$v] = $unidtable[$v] + $this->mConvTable[$v];
+                               } else {
+                                       $this->mConvTable[$v] = $unidtable[$v];
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Parse rules and flags.
+        * @param string|null $variant Variant language code
+        */
+       public function parse( $variant = null ) {
+               if ( !$variant ) {
+                       $variant = $this->mConverter->getPreferredVariant();
+               }
+
+               $this->parseFlags();
+               $flags = $this->mFlags;
+
+               // convert to specified variant
+               // syntax: -{zh-hans;zh-hant[;...]|<text to convert>}-
+               if ( $this->mVariantFlags ) {
+                       // check if current variant in flags
+                       if ( isset( $this->mVariantFlags[$variant] ) ) {
+                               // then convert <text to convert> to current language
+                               $this->mRules = $this->mConverter->autoConvert( $this->mRules,
+                                       $variant );
+                       } else {
+                               // if current variant no in flags,
+                               // then we check its fallback variants.
+                               $variantFallbacks =
+                                       $this->mConverter->getVariantFallbacks( $variant );
+                               if ( is_array( $variantFallbacks ) ) {
+                                       foreach ( $variantFallbacks as $variantFallback ) {
+                                               // if current variant's fallback exist in flags
+                                               if ( isset( $this->mVariantFlags[$variantFallback] ) ) {
+                                                       // then convert <text to convert> to fallback language
+                                                       $this->mRules =
+                                                               $this->mConverter->autoConvert( $this->mRules,
+                                                                       $variantFallback );
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+                       $this->mFlags = $flags = [ 'R' => true ];
+               }
+
+               if ( !isset( $flags['R'] ) && !isset( $flags['N'] ) ) {
+                       // decode => HTML entities modified by Sanitizer::removeHTMLtags
+                       $this->mRules = str_replace( '=&gt;', '=>', $this->mRules );
+                       $this->parseRules();
+               }
+               $rules = $this->mRules;
+
+               if ( !$this->mBidtable && !$this->mUnidtable ) {
+                       if ( isset( $flags['+'] ) || isset( $flags['-'] ) ) {
+                               // fill all variants if text in -{A/H/-|text}- is non-empty but without rules
+                               if ( $rules !== '' ) {
+                                       foreach ( $this->mConverter->mVariants as $v ) {
+                                               $this->mBidtable[$v] = $rules;
+                                       }
+                               }
+                       } elseif ( !isset( $flags['N'] ) && !isset( $flags['T'] ) ) {
+                               $this->mFlags = $flags = [ 'R' => true ];
+                       }
+               }
+
+               $this->mRuleDisplay = false;
+               foreach ( $flags as $flag => $unused ) {
+                       switch ( $flag ) {
+                               case 'R':
+                                       // if we don't do content convert, still strip the -{}- tags
+                                       $this->mRuleDisplay = $rules;
+                                       break;
+                               case 'N':
+                                       // process N flag: output current variant name
+                                       $ruleVar = trim( $rules );
+                                       $this->mRuleDisplay = $this->mConverter->mVariantNames[$ruleVar] ?? '';
+                                       break;
+                               case 'D':
+                                       // process D flag: output rules description
+                                       $this->mRuleDisplay = $this->getRulesDesc();
+                                       break;
+                               case 'H':
+                                       // process H,- flag or T only: output nothing
+                                       $this->mRuleDisplay = '';
+                                       break;
+                               case '-':
+                                       $this->mRulesAction = 'remove';
+                                       $this->mRuleDisplay = '';
+                                       break;
+                               case '+':
+                                       $this->mRulesAction = 'add';
+                                       $this->mRuleDisplay = '';
+                                       break;
+                               case 'S':
+                                       $this->mRuleDisplay = $this->getRuleConvertedStr( $variant );
+                                       break;
+                               case 'T':
+                                       $this->mRuleTitle = $this->getRuleConvertedTitle( $variant );
+                                       $this->mRuleDisplay = '';
+                                       break;
+                               default:
+                                       // ignore unknown flags (but see error case below)
+                       }
+               }
+               if ( $this->mRuleDisplay === false ) {
+                       $this->mRuleDisplay = '<span class="error">'
+                               . wfMessage( 'converter-manual-rule-error' )->inContentLanguage()->escaped()
+                               . '</span>';
+               }
+
+               $this->generateConvTable();
+       }
+
+       /**
+        * Checks if there are conversion rules.
+        * @return bool
+        */
+       public function hasRules() {
+               return $this->mRules !== '';
+       }
+
+       /**
+        * Get display text on markup -{...}-
+        * @return string
+        */
+       public function getDisplay() {
+               return $this->mRuleDisplay;
+       }
+
+       /**
+        * Get converted title.
+        * @return string
+        */
+       public function getTitle() {
+               return $this->mRuleTitle;
+       }
+
+       /**
+        * Return how deal with conversion rules.
+        * @return string
+        */
+       public function getRulesAction() {
+               return $this->mRulesAction;
+       }
+
+       /**
+        * Get conversion table. (bidirectional and unidirectional
+        * conversion table)
+        * @return array
+        */
+       public function getConvTable() {
+               return $this->mConvTable;
+       }
+
+       /**
+        * Get conversion rules string.
+        * @return string
+        */
+       public function getRules() {
+               return $this->mRules;
+       }
+
+       /**
+        * Get conversion flags.
+        * @return array
+        */
+       public function getFlags() {
+               return $this->mFlags;
+       }
+}
index 1d2f0b4..7d954d3 100644 (file)
@@ -21,6 +21,7 @@
 
 /**
  * Methods for dealing with language codes.
+ * @todo Move some of the code-related static methods out of Language into this class
  *
  * @since 1.29
  * @ingroup Language
diff --git a/includes/language/LanguageNameUtils.php b/includes/language/LanguageNameUtils.php
deleted file mode 100644 (file)
index 08d9ab3..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-<?php
-/**
- * Internationalisation code.
- * See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more information.
- *
- * 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 Language
- */
-
-/**
- * @defgroup Language Language
- */
-
-namespace MediaWiki\Languages;
-
-use HashBagOStuff;
-use Hooks;
-use MediaWiki\Config\ServiceOptions;
-use MediaWikiTitleCodec;
-use MWException;
-use Wikimedia\Assert\Assert;
-
-/**
- * @ingroup Language
- *
- * A service that provides utilities to do with language names and codes.
- *
- * @since 1.34
- */
-class LanguageNameUtils {
-       /**
-        * Return autonyms in getLanguageName(s).
-        */
-       const AUTONYMS = null;
-
-       /**
-        * Return all known languages in getLanguageName(s).
-        */
-       const ALL = 'all';
-
-       /**
-        * Return in getLanguageName(s) only the languages that are defined by MediaWiki.
-        */
-       const DEFINED = 'mw';
-
-       /**
-        * Return in getLanguageName(s) only the languages for which we have at least some localisation.
-        */
-       const SUPPORTED = 'mwfile';
-
-       /** @var ServiceOptions */
-       private $options;
-
-       /**
-        * Cache for language names
-        * @var HashBagOStuff|null
-        */
-       private $languageNameCache;
-
-       /**
-        * Cache for validity of language codes
-        * @var array
-        */
-       private $validCodeCache = [];
-
-       public static $constructorOptions = [
-               'ExtraLanguageNames',
-               'UsePigLatinVariant',
-       ];
-
-       /**
-        * @param ServiceOptions $options
-        */
-       public function __construct( ServiceOptions $options ) {
-               $options->assertRequiredOptions( self::$constructorOptions );
-               $this->options = $options;
-       }
-
-       /**
-        * Checks whether any localisation is available for that language tag in MediaWiki
-        * (MessagesXx.php or xx.json exists).
-        *
-        * @param string $code Language tag (in lower case)
-        * @return bool Whether language is supported
-        */
-       public function isSupportedLanguage( $code ) {
-               if ( !$this->isValidBuiltInCode( $code ) ) {
-                       return false;
-               }
-
-               if ( $code === 'qqq' ) {
-                       // Special code for internal use, not supported even though there is a qqq.json
-                       return false;
-               }
-
-               return is_readable( $this->getMessagesFileName( $code ) ) ||
-                       is_readable( $this->getJsonMessagesFileName( $code ) );
-       }
-
-       /**
-        * Returns true if a language code string is of a valid form, whether or not it exists. This
-        * includes codes which are used solely for customisation via the MediaWiki namespace.
-        *
-        * @param string $code
-        *
-        * @return bool
-        */
-       public function isValidCode( $code ) {
-               Assert::parameterType( 'string', $code, '$code' );
-               if ( !isset( $this->validCodeCache[$code] ) ) {
-                       // People think language codes are HTML-safe, so enforce it.  Ideally we should only
-                       // allow a-zA-Z0-9- but .+ and other chars are often used for {{int:}} hacks.  See bugs
-                       // T39564, T39587, T38938.
-                       $this->validCodeCache[$code] =
-                               // Protect against path traversal
-                               strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code ) &&
-                               !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code );
-               }
-               return $this->validCodeCache[$code];
-       }
-
-       /**
-        * Returns true if a language code is of a valid form for the purposes of internal customisation
-        * of MediaWiki, via Messages*.php or *.json.
-        *
-        * @param string $code
-        * @return bool
-        */
-       public function isValidBuiltInCode( $code ) {
-               Assert::parameterType( 'string', $code, '$code' );
-
-               return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
-       }
-
-       /**
-        * Returns true if a language code is an IETF tag known to MediaWiki.
-        *
-        * @param string $tag
-        *
-        * @return bool
-        */
-       public function isKnownLanguageTag( $tag ) {
-               // Quick escape for invalid input to avoid exceptions down the line when code tries to
-               // process tags which are not valid at all.
-               if ( !$this->isValidBuiltInCode( $tag ) ) {
-                       return false;
-               }
-
-               if ( isset( Data\Names::$names[$tag] ) || $this->getLanguageName( $tag, $tag ) !== '' ) {
-                       return true;
-               }
-
-               return false;
-       }
-
-       /**
-        * Get an array of language names, indexed by code.
-        * @param null|string $inLanguage Code of language in which to return the names
-        *   Use self::AUTONYMS for autonyms (native names)
-        * @param string $include One of:
-        *   self::ALL all available languages
-        *   self::DEFINED only if the language is defined in MediaWiki or wgExtraLanguageNames
-        *     (default)
-        *   self::SUPPORTED only if the language is in self::DEFINED *and* has a message file
-        * @return array Language code => language name (sorted by key)
-        */
-       public function getLanguageNames( $inLanguage = self::AUTONYMS, $include = self::DEFINED ) {
-               $cacheKey = $inLanguage === self::AUTONYMS ? 'null' : $inLanguage;
-               $cacheKey .= ":$include";
-               if ( !$this->languageNameCache ) {
-                       $this->languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
-               }
-
-               $ret = $this->languageNameCache->get( $cacheKey );
-               if ( !$ret ) {
-                       $ret = $this->getLanguageNamesUncached( $inLanguage, $include );
-                       $this->languageNameCache->set( $cacheKey, $ret );
-               }
-               return $ret;
-       }
-
-       /**
-        * Uncached helper for getLanguageNames
-        * @param null|string $inLanguage As getLanguageNames
-        * @param string $include As getLanguageNames
-        * @return array Language code => language name (sorted by key)
-        */
-       private function getLanguageNamesUncached( $inLanguage, $include ) {
-               // If passed an invalid language code to use, fallback to en
-               if ( $inLanguage !== self::AUTONYMS && !$this->isValidCode( $inLanguage ) ) {
-                       $inLanguage = 'en';
-               }
-
-               $names = [];
-
-               if ( $inLanguage !== self::AUTONYMS ) {
-                       # TODO: also include for self::AUTONYMS, when this code is more efficient
-                       Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
-               }
-
-               $mwNames = $this->options->get( 'ExtraLanguageNames' ) + Data\Names::$names;
-               if ( $this->options->get( 'UsePigLatinVariant' ) ) {
-                       // Pig Latin (for variant development)
-                       $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
-               }
-
-               foreach ( $mwNames as $mwCode => $mwName ) {
-                       # - Prefer own MediaWiki native name when not using the hook
-                       # - For other names just add if not added through the hook
-                       if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
-                               $names[$mwCode] = $mwName;
-                       }
-               }
-
-               if ( $include === self::ALL ) {
-                       ksort( $names );
-                       return $names;
-               }
-
-               $returnMw = [];
-               $coreCodes = array_keys( $mwNames );
-               foreach ( $coreCodes as $coreCode ) {
-                       $returnMw[$coreCode] = $names[$coreCode];
-               }
-
-               if ( $include === self::SUPPORTED ) {
-                       $namesMwFile = [];
-                       # We do this using a foreach over the codes instead of a directory loop so that messages
-                       # files in extensions will work correctly.
-                       foreach ( $returnMw as $code => $value ) {
-                               if ( is_readable( $this->getMessagesFileName( $code ) ) ||
-                                       is_readable( $this->getJsonMessagesFileName( $code ) )
-                               ) {
-                                       $namesMwFile[$code] = $names[$code];
-                               }
-                       }
-
-                       ksort( $namesMwFile );
-                       return $namesMwFile;
-               }
-
-               ksort( $returnMw );
-               # self::DEFINED option; default if it's not one of the other two options
-               # (self::ALL/self::SUPPORTED)
-               return $returnMw;
-       }
-
-       /**
-        * @param string $code The code of the language for which to get the name
-        * @param null|string $inLanguage Code of language in which to return the name (self::AUTONYMS
-        *   for autonyms)
-        * @param string $include See getLanguageNames(), except this defaults to self::ALL instead of
-        *   self::DEFINED
-        * @return string Language name or empty
-        * @since 1.20
-        */
-       public function getLanguageName( $code, $inLanguage = self::AUTONYMS, $include = self::ALL ) {
-               $code = strtolower( $code );
-               $array = $this->getLanguageNames( $inLanguage, $include );
-               return $array[$code] ?? '';
-       }
-
-       /**
-        * Get the name of a file for a certain language code
-        * @param string $prefix Prepend this to the filename
-        * @param string $code Language code
-        * @param string $suffix Append this to the filename
-        * @throws MWException
-        * @return string $prefix . $mangledCode . $suffix
-        */
-       public function getFileName( $prefix, $code, $suffix = '.php' ) {
-               if ( !$this->isValidBuiltInCode( $code ) ) {
-                       throw new MWException( "Invalid language code \"$code\"" );
-               }
-
-               return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
-       }
-
-       /**
-        * @param string $code
-        * @return string
-        */
-       public function getMessagesFileName( $code ) {
-               global $IP;
-               $file = $this->getFileName( "$IP/languages/messages/Messages", $code, '.php' );
-               Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
-               return $file;
-       }
-
-       /**
-        * @param string $code
-        * @return string
-        * @throws MWException
-        */
-       public function getJsonMessagesFileName( $code ) {
-               global $IP;
-
-               if ( !$this->isValidBuiltInCode( $code ) ) {
-                       throw new MWException( "Invalid language code \"$code\"" );
-               }
-
-               return "$IP/languages/i18n/$code.json";
-       }
-}
index 5c172d6..066d4b4 100644 (file)
@@ -970,17 +970,7 @@ class LoadBalancer implements ILoadBalancer {
                $serverIndex = $conn->getLBInfo( 'serverIndex' );
                $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
                if ( $serverIndex === null || $refCount === null ) {
-                       /**
-                        * This can happen in code like:
-                        *   foreach ( $dbs as $db ) {
-                        *     $conn = $lb->getConnection( $lb::DB_REPLICA, [], $db );
-                        *     ...
-                        *     $lb->reuseConnection( $conn );
-                        *   }
-                        * When a connection to the local DB is opened in this way, reuseConnection()
-                        * should be ignored
-                        */
-                       return;
+                       return; // non-foreign connection; no domain-use tracking to update
                } elseif ( $conn instanceof DBConnRef ) {
                        // DBConnRef already handles calling reuseConnection() and only passes the live
                        // Database instance to this method. Any caller passing in a DBConnRef is broken.
@@ -1312,7 +1302,7 @@ class LoadBalancer implements ILoadBalancer {
 
                // Create a live connection object
                try {
-                       $db = Database::factory( $server['type'], $server );
+                       $conn = Database::factory( $server['type'], $server );
                        // Log when many connection are made on requests
                        ++$this->connectionCounter;
                        $currentConnCount = $this->getCurrentConnectionCount();
@@ -1325,28 +1315,28 @@ class LoadBalancer implements ILoadBalancer {
                } catch ( DBConnectionError $e ) {
                        // FIXME: This is probably the ugliest thing I have ever done to
                        // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
-                       $db = $e->db;
+                       $conn = $e->db;
                }
 
-               $db->setLBInfo( $server );
-               $db->setLazyMasterHandle(
-                       $this->getLazyConnectionRef( self::DB_MASTER, [], $db->getDomainID() )
+               $conn->setLBInfo( $server );
+               $conn->setLazyMasterHandle(
+                       $this->getLazyConnectionRef( self::DB_MASTER, [], $conn->getDomainID() )
                );
-               $db->setTableAliases( $this->tableAliases );
-               $db->setIndexAliases( $this->indexAliases );
+               $conn->setTableAliases( $this->tableAliases );
+               $conn->setIndexAliases( $this->indexAliases );
 
                if ( $server['serverIndex'] === $this->getWriterIndex() ) {
                        if ( $this->trxRoundId !== false ) {
-                               $this->applyTransactionRoundFlags( $db );
+                               $this->applyTransactionRoundFlags( $conn );
                        }
                        foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
-                               $db->setTransactionListener( $name, $callback );
+                               $conn->setTransactionListener( $name, $callback );
                        }
                }
 
                $this->lazyLoadReplicationPositions(); // session consistency
 
-               return $db;
+               return $conn;
        }
 
        /**
@@ -2283,9 +2273,9 @@ class LoadBalancer implements ILoadBalancer {
                ) );
 
                // Update the prefix for all local connections...
-               $this->forEachOpenConnection( function ( IDatabase $db ) use ( $prefix ) {
-                       if ( !$db->getLBInfo( 'foreign' ) ) {
-                               $db->tablePrefix( $prefix );
+               $this->forEachOpenConnection( function ( IDatabase $conn ) use ( $prefix ) {
+                       if ( !$conn->getLBInfo( 'foreign' ) ) {
+                               $conn->tablePrefix( $prefix );
                        }
                } );
        }
index db4838f..e634edc 100644 (file)
@@ -31,6 +31,7 @@ use Wikimedia\Rdbms\DBConnectionError;
 use Wikimedia\Rdbms\IMaintainableDatabase;
 use Wikimedia\Rdbms\LoadBalancer;
 use Wikimedia\ScopedCallback;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
 use Wikimedia\WaitConditionLoop;
 
 /**
@@ -190,28 +191,30 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                $type = $info['type'] ?? 'mysql';
                                $host = $info['host'] ?? '[unknown]';
                                $this->logger->debug( __CLASS__ . ": connecting to $host" );
-                               $db = Database::factory( $type, $info );
-                               $db->clearFlag( DBO_TRX ); // auto-commit mode
-                               $this->conns[$shardIndex] = $db;
+                               $conn = Database::factory( $type, $info );
+                               $conn->clearFlag( DBO_TRX ); // auto-commit mode
+                               $this->conns[$shardIndex] = $conn;
                        }
-                       $db = $this->conns[$shardIndex];
+                       $conn = $this->conns[$shardIndex];
                } else {
                        // Use the main LB database
                        $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
                        $index = $this->replicaOnly ? DB_REPLICA : DB_MASTER;
-                       if ( $lb->getServerType( $lb->getWriterIndex() ) !== 'sqlite' ) {
-                               // Keep a separate connection to avoid contention and deadlocks
-                               $db = $lb->getConnectionRef( $index, [], false, $lb::CONN_TRX_AUTOCOMMIT );
-                       } else {
-                               // However, SQLite has the opposite behavior due to DB-level locking.
-                               // Stock sqlite MediaWiki installs use a separate sqlite cache DB instead.
-                               $db = $lb->getConnectionRef( $index );
+                       // If the RDBMS has row-level locking, use the autocommit connection to avoid
+                       // contention and deadlocks. Do not do this if it only has DB-level locking since
+                       // that would just cause deadlocks.
+                       $attribs = $lb->getServerAttributes( $lb->getWriterIndex() );
+                       $flags = $attribs[Database::ATTR_DB_LEVEL_LOCKING] ? 0 : $lb::CONN_TRX_AUTOCOMMIT;
+                       $conn = $lb->getMaintenanceConnectionRef( $index, [], false, $flags );
+                       // Automatically create the objectcache table for sqlite as needed
+                       if ( $conn->getType() === 'sqlite' ) {
+                               $this->initSqliteDatabase( $conn );
                        }
                }
 
-               $this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $db ) );
+               $this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $conn ) );
 
-               return $db;
+               return $conn;
        }
 
        /**
@@ -582,10 +585,10 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
         * @param string $exptime
         * @return bool
         */
-       private function isExpired( $db, $exptime ) {
+       private function isExpired( IDatabase $db, $exptime ) {
                return (
                        $exptime != $this->getMaxDateTime( $db ) &&
-                       wfTimestamp( TS_UNIX, $exptime ) < $this->getCurrentTime()
+                       ConvertibleTimestamp::convert( TS_UNIX, $exptime ) < $this->getCurrentTime()
                );
        }
 
@@ -687,7 +690,7 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                $serversDoneCount = 0,
                &$keysDeletedCount = 0
        ) {
-               $cutoffUnix = wfTimestamp( TS_UNIX, $timestamp );
+               $cutoffUnix = ConvertibleTimestamp::convert( TS_UNIX, $timestamp );
                $shardIndexes = range( 0, $this->numTableShards - 1 );
                shuffle( $shardIndexes );
 
@@ -709,7 +712,8 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
                                if ( $res->numRows() ) {
                                        $row = $res->current();
                                        if ( $lag === null ) {
-                                               $lag = max( $cutoffUnix - wfTimestamp( TS_UNIX, $row->exptime ), 1 );
+                                               $rowExpUnix = ConvertibleTimestamp::convert( TS_UNIX, $row->exptime );
+                                               $lag = max( $cutoffUnix - $rowExpUnix, 1 );
                                        }
 
                                        $keys = [];
@@ -731,7 +735,8 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
 
                                if ( is_callable( $progressCallback ) ) {
                                        if ( $lag ) {
-                                               $remainingLag = $cutoffUnix - wfTimestamp( TS_UNIX, $continue );
+                                               $continueUnix = ConvertibleTimestamp::convert( TS_UNIX, $continue );
+                                               $remainingLag = $cutoffUnix - $continueUnix;
                                                $processedLag = max( $lag - $remainingLag, 0 );
                                                $doneRatio = ( $numShardsDone + $processedLag / $lag ) / $this->numTableShards;
                                        } else {
@@ -948,20 +953,47 @@ class SqlBagOStuff extends MediumSpecificBagOStuff {
        }
 
        /**
-        * Create shard tables. For use from eval.php.
+        * @param IMaintainableDatabase $db
+        * @throws DBError
+        */
+       private function initSqliteDatabase( IMaintainableDatabase $db ) {
+               if ( $db->tableExists( 'objectcache' ) ) {
+                       return;
+               }
+               // Use one table for SQLite; sharding does not seem to have much benefit
+               $db->query( "PRAGMA journal_mode=WAL" ); // this is permanent
+               $db->startAtomic( __METHOD__ ); // atomic DDL
+               try {
+                       $encTable = $db->tableName( 'objectcache' );
+                       $encExptimeIndex = $db->addIdentifierQuotes( $db->tablePrefix() . 'exptime' );
+                       $db->query(
+                               "CREATE TABLE $encTable (\n" .
+                               "       keyname BLOB NOT NULL default '' PRIMARY KEY,\n" .
+                               "       value BLOB,\n" .
+                               "       exptime TEXT\n" .
+                               ")",
+                               __METHOD__
+                       );
+                       $db->query( "CREATE INDEX $encExptimeIndex ON $encTable (exptime)" );
+                       $db->endAtomic( __METHOD__ );
+               } catch ( DBError $e ) {
+                       $db->rollback( __METHOD__ );
+                       throw $e;
+               }
+       }
+
+       /**
+        * Create the shard tables on all databases (e.g. via eval.php/shell.php)
         */
        public function createTables() {
                for ( $shardIndex = 0; $shardIndex < $this->numServerShards; $shardIndex++ ) {
                        $db = $this->getConnection( $shardIndex );
-                       if ( $db->getType() !== 'mysql' ) {
-                               throw new MWException( __METHOD__ . ' is not supported on this DB server' );
-                       }
-
-                       for ( $i = 0; $i < $this->numTableShards; $i++ ) {
-                               $db->query(
-                                       'CREATE TABLE ' . $db->tableName( $this->getTableNameByShard( $i ) ) .
-                                       ' LIKE ' . $db->tableName( 'objectcache' ),
-                                       __METHOD__ );
+                       if ( in_array( $db->getType(), [ 'mysql', 'postgres' ], true ) ) {
+                               for ( $i = 0; $i < $this->numTableShards; $i++ ) {
+                                       $encBaseTable = $db->tableName( 'objectcache' );
+                                       $encShardTable = $db->tableName( $this->getTableNameByShard( $i ) );
+                                       $db->query( "CREATE TABLE $encShardTable LIKE $encBaseTable" );
+                               }
                        }
                }
        }
index 04021cc..472bcdd 100644 (file)
  * @ingroup Pager
  */
 
-use Wikimedia\Rdbms\IResultWrapper;
-use Wikimedia\Rdbms\IDatabase;
+use MediaWiki\Linker\LinkRenderer;
 use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Navigation\PrevNextNavigationRenderer;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\IResultWrapper;
 
 /**
  * IndexPager is an efficient pager which uses a (roughly unique) index in the
@@ -157,7 +159,10 @@ abstract class IndexPager extends ContextSource implements Pager {
         */
        public $mResult;
 
-       public function __construct( IContextSource $context = null ) {
+       /** @var LinkRenderer */
+       private $linkRenderer;
+
+       public function __construct( IContextSource $context = null, LinkRenderer $linkRenderer = null ) {
                if ( $context ) {
                        $this->setContext( $context );
                }
@@ -209,6 +214,7 @@ abstract class IndexPager extends ContextSource implements Pager {
                                ? $dir[$this->mOrderType]
                                : $dir;
                }
+               $this->linkRenderer = $linkRenderer;
        }
 
        /**
@@ -526,9 +532,9 @@ abstract class IndexPager extends ContextSource implements Pager {
                        $attrs['class'] = "mw-{$type}link";
                }
 
-               return Linker::linkKnown(
+               return $this->getLinkRenderer()->makeKnownLink(
                        $this->getTitle(),
-                       $text,
+                       new HtmlArmor( $text ),
                        $attrs,
                        $query + $this->getDefaultQuery()
                );
@@ -804,4 +810,11 @@ abstract class IndexPager extends ContextSource implements Pager {
 
                return $prevNext->buildPrevNextNavigation( $title, $offset, $limit, $query,  $atend );
        }
+
+       protected function getLinkRenderer() {
+               if ( $this->linkRenderer === null ) {
+                        $this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               }
+               return $this->linkRenderer;
+       }
 }
index d94104b..7f54617 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup Pager
  */
 
+use MediaWiki\Linker\LinkRenderer;
+
 /**
  * Table-based display with a user-selectable sort order
  * @ingroup Pager
@@ -32,10 +34,8 @@ abstract class TablePager extends IndexPager {
        /** @var stdClass */
        protected $mCurrentRow;
 
-       public function __construct( IContextSource $context = null ) {
-               if ( $context ) {
-                       $this->setContext( $context );
-               }
+       public function __construct( IContextSource $context = null, LinkRenderer $linkRenderer = null ) {
+               parent::__construct( $context, $linkRenderer );
 
                $this->mSort = $this->getRequest()->getText( 'sort' );
                if ( !array_key_exists( $this->mSort, $this->getFieldNames() )
@@ -48,8 +48,6 @@ abstract class TablePager extends IndexPager {
                } elseif ( $this->getRequest()->getBool( 'desc' ) ) {
                        $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
                } /* Else leave it at whatever the class default is */
-
-               parent::__construct();
        }
 
        /**
index 2fa8fab..3893e92 100644 (file)
@@ -766,6 +766,33 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                }
        }
 
+       /**
+        * @see $wgRCLinkDays in DefaultSettings.php.
+        * @see $wgRCFilterByAge in DefaultSettings.php.
+        * @return int[]
+        */
+       protected function getLinkDays() {
+               $linkDays = $this->getConfig()->get( 'RCLinkDays' );
+               $filterByAge = $this->getConfig()->get( 'RCFilterByAge' );
+               $maxAge = $this->getConfig()->get( 'RCMaxAge' );
+               if ( $filterByAge ) {
+                       // Trim it to only links which are within $wgRCMaxAge.
+                       // Note that we allow one link higher than the max for things like
+                       // "age 56 days" being accessible through the "60 days" link.
+                       sort( $linkDays );
+
+                       $maxAgeDays = $maxAge / ( 3600 * 24 );
+                       foreach ( $linkDays as $i => $days ) {
+                               if ( $days >= $maxAgeDays ) {
+                                       array_splice( $linkDays, $i + 1 );
+                                       break;
+                               }
+                       }
+               }
+
+               return $linkDays;
+       }
+
        /**
         * Include the modules and configuration for the RCFilters app.
         * Conditional on the user having the feature enabled.
@@ -798,7 +825,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                        'maxDays' => (int)$this->getConfig()->get( 'RCMaxAge' ) / ( 24 * 3600 ), // Translate to days
                                        'limitArray' => $this->getConfig()->get( 'RCLinkLimits' ),
                                        'limitDefault' => $this->getDefaultLimit(),
-                                       'daysArray' => $this->getConfig()->get( 'RCLinkDays' ),
+                                       'daysArray' => $this->getLinkDays(),
                                        'daysDefault' => $this->getDefaultDays(),
                                ]
                        );
index c124c14..6bcca71 100644 (file)
@@ -24,7 +24,7 @@ class SpecialNewSection extends RedirectSpecialPage {
        public function __construct() {
                parent::__construct( 'NewSection' );
                $this->mAllowedRedirectParams = [ 'preloadtitle', 'nosummary', 'editintro',
-                       'preload', 'preloadparams[]', 'summary' ];
+                       'preload', 'preloadparams', 'summary' ];
        }
 
        /**
index 6949c61..30f4655 100644 (file)
@@ -847,7 +847,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                sort( $linkLimits );
                $linkLimits = array_unique( $linkLimits );
 
-               $linkDays = $config->get( 'RCLinkDays' );
+               $linkDays = $this->getLinkDays();
                $linkDays[] = $options['days'];
                sort( $linkDays );
                $linkDays = array_unique( $linkDays );
index 76e2ab7..45d77ce 100644 (file)
@@ -235,7 +235,7 @@ class AllMessagesTablePager extends TablePager {
        }
 
        function formatValue( $field, $value ) {
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
                switch ( $field ) {
                        case 'am_title' :
                                $title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
@@ -256,8 +256,7 @@ class AllMessagesTablePager extends TablePager {
                                        $title = $linkRenderer->makeKnownLink( $title, $this->getLanguage()->lcfirst( $value ) );
                                } else {
                                        $title = $linkRenderer->makeBrokenLink(
-                                               $title,
-                                               $this->getLanguage()->lcfirst( $value )
+                                               $title, $this->getLanguage()->lcfirst( $value )
                                        );
                                }
                                if ( $this->mCurrentRow->am_talk_exists ) {
index 01aed22..77b7326 100644 (file)
@@ -45,9 +45,9 @@ class BlockListPager extends TablePager {
         * @param array $conds
         */
        public function __construct( $page, $conds ) {
+               parent::__construct( $page->getContext(), $page->getLinkRenderer() );
                $this->conds = $conds;
                $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
-               parent::__construct( $page->getContext() );
        }
 
        function getFieldNames() {
@@ -97,7 +97,7 @@ class BlockListPager extends TablePager {
 
                $formatted = '';
 
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
 
                switch ( $name ) {
                        case 'ipb_timestamp':
@@ -250,7 +250,7 @@ class BlockListPager extends TablePager {
         */
        private function getRestrictionListHTML( stdClass $row ) {
                $items = [];
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
 
                foreach ( $this->restrictions as $restriction ) {
                        if ( $restriction->getBlockId() !== (int)$row->ipb_id ) {
index 7db90c1..db2ee38 100644 (file)
@@ -25,11 +25,6 @@ use MediaWiki\Linker\LinkRenderer;
  */
 class CategoryPager extends AlphabeticPager {
 
-       /**
-        * @var LinkRenderer
-        */
-       protected $linkRenderer;
-
        /**
         * @param IContextSource $context
         * @param string $from
@@ -37,15 +32,13 @@ class CategoryPager extends AlphabeticPager {
         */
        public function __construct( IContextSource $context, $from, LinkRenderer $linkRenderer
        ) {
-               parent::__construct( $context );
+               parent::__construct( $context, $linkRenderer );
                $from = str_replace( ' ', '_', $from );
                if ( $from !== '' ) {
                        $from = Title::capitalize( $from, NS_CATEGORY );
                        $this->setOffset( $from );
                        $this->setIncludeOffset( true );
                }
-
-               $this->linkRenderer = $linkRenderer;
        }
 
        function getQueryInfo() {
@@ -85,7 +78,7 @@ class CategoryPager extends AlphabeticPager {
        function formatRow( $result ) {
                $title = new TitleValue( NS_CATEGORY, $result->cat_title );
                $text = $title->getText();
-               $link = $this->linkRenderer->makeLink( $title, $text );
+               $link = $this->getLinkRenderer()->makeLink( $title, $text );
 
                $count = $this->msg( 'nmembers' )->numParams( $result->cat_pages )->escaped();
                return Html::rawElement( 'li', null, $this->getLanguage()->specialList( $link, $count ) ) . "\n";
index 9ac7df5..1b0c59a 100644 (file)
@@ -621,7 +621,7 @@ class ContribsPager extends RangeChronologicalPager {
                $classes = [];
                $attribs = [];
 
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
 
                $page = null;
                // Create a title for the revision if possible
index 2f40ace..7dbfae8 100644 (file)
@@ -289,7 +289,7 @@ class DeletedContribsPager extends IndexPager {
        function formatRevisionRow( $row ) {
                $page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
 
-               $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
 
                $rev = new Revision( [
                        'title' => $page,
index 2d3b6b2..6bdccd8 100644 (file)
@@ -53,7 +53,8 @@ class ImageListPager extends TablePager {
        public function __construct( IContextSource $context, $userName = null, $search = '',
                $including = false, $showAll = false
        ) {
-               $this->setContext( $context );
+               parent::__construct( $context );
+
                $this->mIncluding = $including;
                $this->mShowAll = $showAll;
 
@@ -94,8 +95,6 @@ class ImageListPager extends TablePager {
                } else {
                        $this->mDefaultDirection = IndexPager::DIR_DESCENDING;
                }
-
-               parent::__construct( $context );
        }
 
        /**
@@ -437,7 +436,7 @@ class ImageListPager extends TablePager {
         */
        function formatValue( $field, $value ) {
                $services = MediaWikiServices::getInstance();
-               $linkRenderer = $services->getLinkRenderer();
+               $linkRenderer = $this->getLinkRenderer();
                switch ( $field ) {
                        case 'thumb':
                                $opt = [ 'time' => wfTimestamp( TS_MW, $this->mCurrentRow->img_timestamp ) ];
@@ -478,7 +477,7 @@ class ImageListPager extends TablePager {
 
                                        // Add delete links if allowed
                                        // From https://github.com/Wikia/app/pull/3859
-                                       $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+                                       $permissionManager = $services->getPermissionManager();
 
                                        if ( $permissionManager->userCan( 'delete', $this->getUser(), $filePage ) ) {
                                                $deleteMsg = $this->msg( 'listfiles-delete' )->text();
index ed86e54..57db8b3 100644 (file)
@@ -194,7 +194,7 @@ class NewFilesPager extends RangeChronologicalPager {
                $user = User::newFromId( $row->img_user );
 
                $title = Title::makeTitle( NS_FILE, $name );
-               $ul = MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
+               $ul = $this->getLinkRenderer()->makeLink(
                        $user->getUserPage(),
                        $user->getName()
                );
index 5583842..747dea2 100644 (file)
@@ -26,11 +26,6 @@ class ProtectedPagesPager extends TablePager {
        public $mConds;
        private $type, $level, $namespace, $sizetype, $size, $indefonly, $cascadeonly, $noredirect;
 
-       /**
-        * @var LinkRenderer
-        */
-       private $linkRenderer;
-
        /**
         * @param SpecialPage $form
         * @param array $conds
@@ -48,6 +43,7 @@ class ProtectedPagesPager extends TablePager {
                $sizetype, $size, $indefonly, $cascadeonly, $noredirect,
                LinkRenderer $linkRenderer
        ) {
+               parent::__construct( $form->getContext(), $linkRenderer );
                $this->mConds = $conds;
                $this->type = $type ?: 'edit';
                $this->level = $level;
@@ -57,8 +53,6 @@ class ProtectedPagesPager extends TablePager {
                $this->indefonly = (bool)$indefonly;
                $this->cascadeonly = (bool)$cascadeonly;
                $this->noredirect = (bool)$noredirect;
-               $this->linkRenderer = $linkRenderer;
-               parent::__construct( $form->getContext() );
        }
 
        function preprocessResults( $result ) {
@@ -119,6 +113,7 @@ class ProtectedPagesPager extends TablePager {
        function formatValue( $field, $value ) {
                /** @var object $row */
                $row = $this->mCurrentRow;
+               $linkRenderer = $this->getLinkRenderer();
 
                switch ( $field ) {
                        case 'log_timestamp':
@@ -148,7 +143,7 @@ class ProtectedPagesPager extends TablePager {
                                                )
                                        );
                                } else {
-                                       $formatted = $this->linkRenderer->makeLink( $title );
+                                       $formatted = $linkRenderer->makeLink( $title );
                                }
                                if ( !is_null( $row->page_len ) ) {
                                        $formatted .= $this->getLanguage()->getDirMark() .
@@ -165,7 +160,7 @@ class ProtectedPagesPager extends TablePager {
                                        $value, /* User preference timezone */true ) );
                                $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
                                if ( $this->getUser()->isAllowed( 'protect' ) && $title ) {
-                                       $changeProtection = $this->linkRenderer->makeKnownLink(
+                                       $changeProtection = $linkRenderer->makeKnownLink(
                                                $title,
                                                $this->msg( 'protect_change' )->text(),
                                                [],
index 57b575b..e27fb58 100644 (file)
@@ -44,10 +44,6 @@ class UsersPager extends AlphabeticPager {
         * another page
         */
        public function __construct( IContextSource $context = null, $par = null, $including = null ) {
-               if ( $context ) {
-                       $this->setContext( $context );
-               }
-
                $request = $this->getRequest();
                $par = $par ?? '';
                $parms = explode( '/', $par );
@@ -87,7 +83,7 @@ class UsersPager extends AlphabeticPager {
                        }
                }
 
-               parent::__construct();
+               parent::__construct( $context );
        }
 
        /**
diff --git a/languages/ConverterRule.php b/languages/ConverterRule.php
deleted file mode 100644 (file)
index 4a330ad..0000000
+++ /dev/null
@@ -1,498 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Language
- */
-
-/**
- * Parser for rules of language conversion, parse rules in -{ }- tag.
- * @ingroup Language
- * @author fdcn <fdcn64@gmail.com>, PhiLiP <philip.npc@gmail.com>
- */
-class ConverterRule {
-       public $mText; // original text in -{text}-
-       public $mConverter; // LanguageConverter object
-       public $mRuleDisplay = '';
-       public $mRuleTitle = false;
-       public $mRules = ''; // string : the text of the rules
-       public $mRulesAction = 'none';
-       public $mFlags = [];
-       public $mVariantFlags = [];
-       public $mConvTable = [];
-       public $mBidtable = []; // array of the translation in each variant
-       public $mUnidtable = []; // array of the translation in each variant
-
-       /**
-        * @param string $text The text between -{ and }-
-        * @param LanguageConverter $converter
-        */
-       public function __construct( $text, $converter ) {
-               $this->mText = $text;
-               $this->mConverter = $converter;
-       }
-
-       /**
-        * Check if variants array in convert array.
-        *
-        * @param array|string $variants Variant language code
-        * @return string Translated text
-        */
-       public function getTextInBidtable( $variants ) {
-               $variants = (array)$variants;
-               if ( !$variants ) {
-                       return false;
-               }
-               foreach ( $variants as $variant ) {
-                       if ( isset( $this->mBidtable[$variant] ) ) {
-                               return $this->mBidtable[$variant];
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Parse flags with syntax -{FLAG| ... }-
-        * @private
-        */
-       function parseFlags() {
-               $text = $this->mText;
-               $flags = [];
-               $variantFlags = [];
-
-               $sepPos = strpos( $text, '|' );
-               if ( $sepPos !== false ) {
-                       $validFlags = $this->mConverter->mFlags;
-                       $f = StringUtils::explode( ';', substr( $text, 0, $sepPos ) );
-                       foreach ( $f as $ff ) {
-                               $ff = trim( $ff );
-                               if ( isset( $validFlags[$ff] ) ) {
-                                       $flags[$validFlags[$ff]] = true;
-                               }
-                       }
-                       $text = strval( substr( $text, $sepPos + 1 ) );
-               }
-
-               if ( !$flags ) {
-                       $flags['S'] = true;
-               } elseif ( isset( $flags['R'] ) ) {
-                       $flags = [ 'R' => true ];// remove other flags
-               } elseif ( isset( $flags['N'] ) ) {
-                       $flags = [ 'N' => true ];// remove other flags
-               } elseif ( isset( $flags['-'] ) ) {
-                       $flags = [ '-' => true ];// remove other flags
-               } elseif ( count( $flags ) == 1 && isset( $flags['T'] ) ) {
-                       $flags['H'] = true;
-               } elseif ( isset( $flags['H'] ) ) {
-                       // replace A flag, and remove other flags except T
-                       $temp = [ '+' => true, 'H' => true ];
-                       if ( isset( $flags['T'] ) ) {
-                               $temp['T'] = true;
-                       }
-                       if ( isset( $flags['D'] ) ) {
-                               $temp['D'] = true;
-                       }
-                       $flags = $temp;
-               } else {
-                       if ( isset( $flags['A'] ) ) {
-                               $flags['+'] = true;
-                               $flags['S'] = true;
-                       }
-                       if ( isset( $flags['D'] ) ) {
-                               unset( $flags['S'] );
-                       }
-                       // try to find flags like "zh-hans", "zh-hant"
-                       // allow syntaxes like "-{zh-hans;zh-hant|XXXX}-"
-                       $variantFlags = array_intersect( array_keys( $flags ), $this->mConverter->mVariants );
-                       if ( $variantFlags ) {
-                               $variantFlags = array_flip( $variantFlags );
-                               $flags = [];
-                       }
-               }
-               $this->mVariantFlags = $variantFlags;
-               $this->mRules = $text;
-               $this->mFlags = $flags;
-       }
-
-       /**
-        * Generate conversion table.
-        * @private
-        */
-       function parseRules() {
-               $rules = $this->mRules;
-               $bidtable = [];
-               $unidtable = [];
-               $variants = $this->mConverter->mVariants;
-               $varsep_pattern = $this->mConverter->getVarSeparatorPattern();
-
-               // Split according to $varsep_pattern, but ignore semicolons from HTML entities
-               $rules = preg_replace( '/(&[#a-zA-Z0-9]+);/', "$1\x01", $rules );
-               $choice = preg_split( $varsep_pattern, $rules );
-               $choice = str_replace( "\x01", ';', $choice );
-
-               foreach ( $choice as $c ) {
-                       $v = explode( ':', $c, 2 );
-                       if ( count( $v ) != 2 ) {
-                               // syntax error, skip
-                               continue;
-                       }
-                       $to = trim( $v[1] );
-                       $v = trim( $v[0] );
-                       $u = explode( '=>', $v, 2 );
-                       $vv = $this->mConverter->validateVariant( $v );
-                       // if $to is empty (which is also used as $from in bidtable),
-                       // strtr() could return a wrong result.
-                       if ( count( $u ) == 1 && $to !== '' && $vv ) {
-                               $bidtable[$vv] = $to;
-                       } elseif ( count( $u ) == 2 ) {
-                               $from = trim( $u[0] );
-                               $v = trim( $u[1] );
-                               $vv = $this->mConverter->validateVariant( $v );
-                               // if $from is empty, strtr() could return a wrong result.
-                               if ( array_key_exists( $vv, $unidtable )
-                                       && !is_array( $unidtable[$vv] )
-                                       && $from !== ''
-                                       && $vv ) {
-                                       $unidtable[$vv] = [ $from => $to ];
-                               } elseif ( $from !== '' && $vv ) {
-                                       $unidtable[$vv][$from] = $to;
-                               }
-                       }
-                       // syntax error, pass
-                       if ( !isset( $this->mConverter->mVariantNames[$vv] ) ) {
-                               $bidtable = [];
-                               $unidtable = [];
-                               break;
-                       }
-               }
-               $this->mBidtable = $bidtable;
-               $this->mUnidtable = $unidtable;
-       }
-
-       /**
-        * @private
-        *
-        * @return string
-        */
-       function getRulesDesc() {
-               $codesep = $this->mConverter->mDescCodeSep;
-               $varsep = $this->mConverter->mDescVarSep;
-               $text = '';
-               foreach ( $this->mBidtable as $k => $v ) {
-                       $text .= $this->mConverter->mVariantNames[$k] . "$codesep$v$varsep";
-               }
-               foreach ( $this->mUnidtable as $k => $a ) {
-                       foreach ( $a as $from => $to ) {
-                               $text .= $from . '⇒' . $this->mConverter->mVariantNames[$k] .
-                                       "$codesep$to$varsep";
-                       }
-               }
-               return $text;
-       }
-
-       /**
-        * Parse rules conversion.
-        * @private
-        *
-        * @param string $variant
-        *
-        * @return string
-        */
-       function getRuleConvertedStr( $variant ) {
-               $bidtable = $this->mBidtable;
-               $unidtable = $this->mUnidtable;
-
-               if ( count( $bidtable ) + count( $unidtable ) == 0 ) {
-                       return $this->mRules;
-               } else {
-                       // display current variant in bidirectional array
-                       $disp = $this->getTextInBidtable( $variant );
-                       // or display current variant in fallbacks
-                       if ( $disp === false ) {
-                               $disp = $this->getTextInBidtable(
-                                       $this->mConverter->getVariantFallbacks( $variant ) );
-                       }
-                       // or display current variant in unidirectional array
-                       if ( $disp === false && array_key_exists( $variant, $unidtable ) ) {
-                               $disp = array_values( $unidtable[$variant] )[0];
-                       }
-                       // or display first text under disable manual convert
-                       if ( $disp === false && $this->mConverter->mManualLevel[$variant] == 'disable' ) {
-                               if ( count( $bidtable ) > 0 ) {
-                                       $disp = array_values( $bidtable )[0];
-                               } else {
-                                       $disp = array_values( array_values( $unidtable )[0] )[0];
-                               }
-                       }
-                       return $disp;
-               }
-       }
-
-       /**
-        * Similar to getRuleConvertedStr(), but this prefers to use original
-        * page title if $variant === $this->mConverter->mMainLanguageCode
-        * and may return false in this case (so this title conversion rule
-        * will be ignored and the original title is shown).
-        *
-        * @since 1.22
-        * @param string $variant The variant code to display page title in
-        * @return string|bool The converted title or false if just page name
-        */
-       function getRuleConvertedTitle( $variant ) {
-               if ( $variant === $this->mConverter->mMainLanguageCode ) {
-                       // If a string targeting exactly this variant is set,
-                       // use it. Otherwise, just return false, so the real
-                       // page name can be shown (and because variant === main,
-                       // there'll be no further automatic conversion).
-                       $disp = $this->getTextInBidtable( $variant );
-                       if ( $disp ) {
-                               return $disp;
-                       }
-                       if ( array_key_exists( $variant, $this->mUnidtable ) ) {
-                               $disp = array_values( $this->mUnidtable[$variant] )[0];
-                       }
-                       // Assigned above or still false.
-                       return $disp;
-               } else {
-                       return $this->getRuleConvertedStr( $variant );
-               }
-       }
-
-       /**
-        * Generate conversion table for all text.
-        * @private
-        */
-       function generateConvTable() {
-               // Special case optimisation
-               if ( !$this->mBidtable && !$this->mUnidtable ) {
-                       $this->mConvTable = [];
-                       return;
-               }
-
-               $bidtable = $this->mBidtable;
-               $unidtable = $this->mUnidtable;
-               $manLevel = $this->mConverter->mManualLevel;
-
-               $vmarked = [];
-               foreach ( $this->mConverter->mVariants as $v ) {
-                       /* for bidirectional array
-                               fill in the missing variants, if any,
-                               with fallbacks */
-                       if ( !isset( $bidtable[$v] ) ) {
-                               $variantFallbacks =
-                                       $this->mConverter->getVariantFallbacks( $v );
-                               $vf = $this->getTextInBidtable( $variantFallbacks );
-                               if ( $vf ) {
-                                       $bidtable[$v] = $vf;
-                               }
-                       }
-
-                       if ( isset( $bidtable[$v] ) ) {
-                               foreach ( $vmarked as $vo ) {
-                                       // use syntax: -{A|zh:WordZh;zh-tw:WordTw}-
-                                       // or -{H|zh:WordZh;zh-tw:WordTw}-
-                                       // or -{-|zh:WordZh;zh-tw:WordTw}-
-                                       // to introduce a custom mapping between
-                                       // words WordZh and WordTw in the whole text
-                                       if ( $manLevel[$v] == 'bidirectional' ) {
-                                               $this->mConvTable[$v][$bidtable[$vo]] = $bidtable[$v];
-                                       }
-                                       if ( $manLevel[$vo] == 'bidirectional' ) {
-                                               $this->mConvTable[$vo][$bidtable[$v]] = $bidtable[$vo];
-                                       }
-                               }
-                               $vmarked[] = $v;
-                       }
-                       /* for unidirectional array fill to convert tables */
-                       if ( ( $manLevel[$v] == 'bidirectional' || $manLevel[$v] == 'unidirectional' )
-                               && isset( $unidtable[$v] )
-                       ) {
-                               if ( isset( $this->mConvTable[$v] ) ) {
-                                       $this->mConvTable[$v] = $unidtable[$v] + $this->mConvTable[$v];
-                               } else {
-                                       $this->mConvTable[$v] = $unidtable[$v];
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Parse rules and flags.
-        * @param string|null $variant Variant language code
-        */
-       public function parse( $variant = null ) {
-               if ( !$variant ) {
-                       $variant = $this->mConverter->getPreferredVariant();
-               }
-
-               $this->parseFlags();
-               $flags = $this->mFlags;
-
-               // convert to specified variant
-               // syntax: -{zh-hans;zh-hant[;...]|<text to convert>}-
-               if ( $this->mVariantFlags ) {
-                       // check if current variant in flags
-                       if ( isset( $this->mVariantFlags[$variant] ) ) {
-                               // then convert <text to convert> to current language
-                               $this->mRules = $this->mConverter->autoConvert( $this->mRules,
-                                       $variant );
-                       } else {
-                               // if current variant no in flags,
-                               // then we check its fallback variants.
-                               $variantFallbacks =
-                                       $this->mConverter->getVariantFallbacks( $variant );
-                               if ( is_array( $variantFallbacks ) ) {
-                                       foreach ( $variantFallbacks as $variantFallback ) {
-                                               // if current variant's fallback exist in flags
-                                               if ( isset( $this->mVariantFlags[$variantFallback] ) ) {
-                                                       // then convert <text to convert> to fallback language
-                                                       $this->mRules =
-                                                               $this->mConverter->autoConvert( $this->mRules,
-                                                                       $variantFallback );
-                                                       break;
-                                               }
-                                       }
-                               }
-                       }
-                       $this->mFlags = $flags = [ 'R' => true ];
-               }
-
-               if ( !isset( $flags['R'] ) && !isset( $flags['N'] ) ) {
-                       // decode => HTML entities modified by Sanitizer::removeHTMLtags
-                       $this->mRules = str_replace( '=&gt;', '=>', $this->mRules );
-                       $this->parseRules();
-               }
-               $rules = $this->mRules;
-
-               if ( !$this->mBidtable && !$this->mUnidtable ) {
-                       if ( isset( $flags['+'] ) || isset( $flags['-'] ) ) {
-                               // fill all variants if text in -{A/H/-|text}- is non-empty but without rules
-                               if ( $rules !== '' ) {
-                                       foreach ( $this->mConverter->mVariants as $v ) {
-                                               $this->mBidtable[$v] = $rules;
-                                       }
-                               }
-                       } elseif ( !isset( $flags['N'] ) && !isset( $flags['T'] ) ) {
-                               $this->mFlags = $flags = [ 'R' => true ];
-                       }
-               }
-
-               $this->mRuleDisplay = false;
-               foreach ( $flags as $flag => $unused ) {
-                       switch ( $flag ) {
-                               case 'R':
-                                       // if we don't do content convert, still strip the -{}- tags
-                                       $this->mRuleDisplay = $rules;
-                                       break;
-                               case 'N':
-                                       // process N flag: output current variant name
-                                       $ruleVar = trim( $rules );
-                                       $this->mRuleDisplay = $this->mConverter->mVariantNames[$ruleVar] ?? '';
-                                       break;
-                               case 'D':
-                                       // process D flag: output rules description
-                                       $this->mRuleDisplay = $this->getRulesDesc();
-                                       break;
-                               case 'H':
-                                       // process H,- flag or T only: output nothing
-                                       $this->mRuleDisplay = '';
-                                       break;
-                               case '-':
-                                       $this->mRulesAction = 'remove';
-                                       $this->mRuleDisplay = '';
-                                       break;
-                               case '+':
-                                       $this->mRulesAction = 'add';
-                                       $this->mRuleDisplay = '';
-                                       break;
-                               case 'S':
-                                       $this->mRuleDisplay = $this->getRuleConvertedStr( $variant );
-                                       break;
-                               case 'T':
-                                       $this->mRuleTitle = $this->getRuleConvertedTitle( $variant );
-                                       $this->mRuleDisplay = '';
-                                       break;
-                               default:
-                                       // ignore unknown flags (but see error case below)
-                       }
-               }
-               if ( $this->mRuleDisplay === false ) {
-                       $this->mRuleDisplay = '<span class="error">'
-                               . wfMessage( 'converter-manual-rule-error' )->inContentLanguage()->escaped()
-                               . '</span>';
-               }
-
-               $this->generateConvTable();
-       }
-
-       /**
-        * Checks if there are conversion rules.
-        * @return bool
-        */
-       public function hasRules() {
-               return $this->mRules !== '';
-       }
-
-       /**
-        * Get display text on markup -{...}-
-        * @return string
-        */
-       public function getDisplay() {
-               return $this->mRuleDisplay;
-       }
-
-       /**
-        * Get converted title.
-        * @return string
-        */
-       public function getTitle() {
-               return $this->mRuleTitle;
-       }
-
-       /**
-        * Return how deal with conversion rules.
-        * @return string
-        */
-       public function getRulesAction() {
-               return $this->mRulesAction;
-       }
-
-       /**
-        * Get conversion table. (bidirectional and unidirectional
-        * conversion table)
-        * @return array
-        */
-       public function getConvTable() {
-               return $this->mConvTable;
-       }
-
-       /**
-        * Get conversion rules string.
-        * @return string
-        */
-       public function getRules() {
-               return $this->mRules;
-       }
-
-       /**
-        * Get conversion flags.
-        * @return array
-        */
-       public function getFlags() {
-               return $this->mFlags;
-       }
-}
index 872614c..bb256c9 100644 (file)
@@ -27,8 +27,8 @@
  */
 
 use CLDRPluralRuleParser\Evaluator;
-use MediaWiki\Languages\LanguageNameUtils;
 use MediaWiki\MediaWikiServices;
+use Wikimedia\Assert\Assert;
 
 /**
  * Internationalisation code
@@ -38,24 +38,21 @@ class Language {
        /**
         * Return autonyms in fetchLanguageName(s).
         * @since 1.32
-        * @deprecated since 1.34, LanguageNameUtils::AUTONYMS
         */
-       const AS_AUTONYMS = LanguageNameUtils::AUTONYMS;
+       const AS_AUTONYMS = null;
 
        /**
         * Return all known languages in fetchLanguageName(s).
         * @since 1.32
-        * @deprecated since 1.34, use LanguageNameUtils::ALL
         */
-       const ALL = LanguageNameUtils::ALL;
+       const ALL = 'all';
 
        /**
         * Return in fetchLanguageName(s) only the languages for which we have at
         * least some localisation.
         * @since 1.32
-        * @deprecated since 1.34, use LanguageNameUtils::SUPPORTED
         */
-       const SUPPORTED = LanguageNameUtils::SUPPORTED;
+       const SUPPORTED = 'mwfile';
 
        /**
         * @var LanguageConverter
@@ -78,11 +75,10 @@ class Language {
         */
        public $transformData = [];
 
-       /** @var LocalisationCache */
-       private $localisationCache;
-
-       /** @var LanguageNameUtils */
-       private $langNameUtils;
+       /**
+        * @var LocalisationCache
+        */
+       public static $dataCache;
 
        public static $mLangObjCache = [];
 
@@ -98,7 +94,6 @@ class Language {
         */
        const STRICT_FALLBACKS = 1;
 
-       // TODO Make these const once we drop HHVM support (T192166)
        public static $mWeekdayMsgs = [
                'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
                'friday', 'saturday'
@@ -183,6 +178,12 @@ class Language {
         */
        private static $grammarTransformations;
 
+       /**
+        * Cache for language names
+        * @var HashBagOStuff|null
+        */
+       private static $languageNameCache;
+
        /**
         * Unicode directional formatting characters, for embedBidi()
         */
@@ -238,12 +239,11 @@ class Language {
         * @return Language
         */
        protected static function newFromCode( $code, $fallback = false ) {
-               $langNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils();
-               if ( !$langNameUtils->isValidCode( $code ) ) {
+               if ( !self::isValidCode( $code ) ) {
                        throw new MWException( "Invalid language code \"$code\"" );
                }
 
-               if ( !$langNameUtils->isValidBuiltInCode( $code ) ) {
+               if ( !self::isValidBuiltInCode( $code ) ) {
                        // It's not possible to customise this code with class files, so
                        // just return a Language object. This is to support uselang= hacks.
                        $lang = new Language;
@@ -262,7 +262,7 @@ class Language {
                // Keep trying the fallback list until we find an existing class
                $fallbacks = self::getFallbacksFor( $code );
                foreach ( $fallbacks as $fallbackCode ) {
-                       if ( !$langNameUtils->isValidBuiltInCode( $fallbackCode ) ) {
+                       if ( !self::isValidBuiltInCode( $fallbackCode ) ) {
                                throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
                        }
 
@@ -283,30 +283,37 @@ class Language {
         * @since 1.32
         */
        public static function clearCaches() {
-               if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MEDIAWIKI_INSTALL' ) ) {
-                       throw new MWException( __METHOD__ . ' must not be used outside tests/installer' );
-               }
-               if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
-                       MediaWikiServices::getInstance()->resetServiceForTesting( 'LocalisationCache' );
-                       MediaWikiServices::getInstance()->resetServiceForTesting( 'LanguageNameUtils' );
+               if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+                       throw new MWException( __METHOD__ . ' must not be used outside tests' );
                }
+               self::$dataCache = null;
+               // Reinitialize $dataCache, since it's expected to always be available
+               self::getLocalisationCache();
                self::$mLangObjCache = [];
                self::$fallbackLanguageCache = [];
                self::$grammarTransformations = null;
+               self::$languageNameCache = null;
        }
 
        /**
         * Checks whether any localisation is available for that language tag
         * in MediaWiki (MessagesXx.php exists).
         *
-        * @deprecated since 1.34, use LanguageNameUtils
         * @param string $code Language tag (in lower case)
         * @return bool Whether language is supported
         * @since 1.21
         */
        public static function isSupportedLanguage( $code ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->isSupportedLanguage( $code );
+               if ( !self::isValidBuiltInCode( $code ) ) {
+                       return false;
+               }
+
+               if ( $code === 'qqq' ) {
+                       return false;
+               }
+
+               return is_readable( self::getMessagesFileName( $code ) ) ||
+                       is_readable( self::getJsonMessagesFileName( $code ) );
        }
 
        /**
@@ -374,55 +381,77 @@ class Language {
         * not it exists. This includes codes which are used solely for
         * customisation via the MediaWiki namespace.
         *
-        * @deprecated since 1.34, use LanguageNameUtils
-        *
         * @param string $code
         *
         * @return bool
         */
        public static function isValidCode( $code ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()->isValidCode( $code );
+               static $cache = [];
+               Assert::parameterType( 'string', $code, '$code' );
+               if ( !isset( $cache[$code] ) ) {
+                       // People think language codes are html safe, so enforce it.
+                       // Ideally we should only allow a-zA-Z0-9-
+                       // but, .+ and other chars are often used for {{int:}} hacks
+                       // see bugs T39564, T39587, T38938
+                       $cache[$code] =
+                               // Protect against path traversal
+                               strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
+                               && !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code );
+               }
+               return $cache[$code];
        }
 
        /**
         * Returns true if a language code is of a valid form for the purposes of
         * internal customisation of MediaWiki, via Messages*.php or *.json.
         *
-        * @deprecated since 1.34, use LanguageNameUtils
-        *
         * @param string $code
         *
         * @since 1.18
         * @return bool
         */
        public static function isValidBuiltInCode( $code ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->isValidBuiltInCode( $code );
+               Assert::parameterType( 'string', $code, '$code' );
+
+               return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
        }
 
        /**
         * Returns true if a language code is an IETF tag known to MediaWiki.
         *
-        * @deprecated since 1.34, use LanguageNameUtils
-        *
         * @param string $tag
         *
         * @since 1.21
         * @return bool
         */
        public static function isKnownLanguageTag( $tag ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->isKnownLanguageTag( $tag );
+               // Quick escape for invalid input to avoid exceptions down the line
+               // when code tries to process tags which are not valid at all.
+               if ( !self::isValidBuiltInCode( $tag ) ) {
+                       return false;
+               }
+
+               if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
+                       || self::fetchLanguageName( $tag, $tag ) !== ''
+               ) {
+                       return true;
+               }
+
+               return false;
        }
 
        /**
         * Get the LocalisationCache instance
         *
-        * @deprecated since 1.34, use MediaWikiServices
         * @return LocalisationCache
         */
        public static function getLocalisationCache() {
-               return MediaWikiServices::getInstance()->getLocalisationCache();
+               if ( is_null( self::$dataCache ) ) {
+                       global $wgLocalisationCacheConf;
+                       $class = $wgLocalisationCacheConf['class'];
+                       self::$dataCache = new $class( $wgLocalisationCacheConf );
+               }
+               return self::$dataCache;
        }
 
        function __construct() {
@@ -433,9 +462,7 @@ class Language {
                } else {
                        $this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
                }
-               $services = MediaWikiServices::getInstance();
-               $this->localisationCache = $services->getLocalisationCache();
-               $this->langNameUtils = $services->getLanguageNameUtils();
+               self::getLocalisationCache();
        }
 
        /**
@@ -467,7 +494,7 @@ class Language {
         * @return array
         */
        public function getBookstoreList() {
-               return $this->localisationCache->getItem( $this->mCode, 'bookstoreList' );
+               return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
        }
 
        /**
@@ -484,7 +511,7 @@ class Language {
                                getCanonicalNamespaces();
 
                        $this->namespaceNames = $wgExtraNamespaces +
-                               $this->localisationCache->getItem( $this->mCode, 'namespaceNames' );
+                               self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
                        $this->namespaceNames += $validNamespaces;
 
                        $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
@@ -591,7 +618,7 @@ class Language {
                global $wgExtraGenderNamespaces;
 
                $ns = $wgExtraGenderNamespaces +
-                       (array)$this->localisationCache->getItem( $this->mCode, 'namespaceGenderAliases' );
+                       (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
 
                return $ns[$index][$gender] ?? $this->getNsText( $index );
        }
@@ -613,7 +640,7 @@ class Language {
                        return false;
                } else {
                        // Check what is in i18n files
-                       $aliases = $this->localisationCache->getItem( $this->mCode, 'namespaceGenderAliases' );
+                       $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
                        return count( $aliases ) > 0;
                }
        }
@@ -637,7 +664,7 @@ class Language {
         */
        public function getNamespaceAliases() {
                if ( is_null( $this->namespaceAliases ) ) {
-                       $aliases = $this->localisationCache->getItem( $this->mCode, 'namespaceAliases' );
+                       $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
                        if ( !$aliases ) {
                                $aliases = [];
                        } else {
@@ -651,8 +678,8 @@ class Language {
                        }
 
                        global $wgExtraGenderNamespaces;
-                       $genders = $wgExtraGenderNamespaces + (array)$this->localisationCache
-                               ->getItem( $this->mCode, 'namespaceGenderAliases' );
+                       $genders = $wgExtraGenderNamespaces +
+                               (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
                        foreach ( $genders as $index => $forms ) {
                                foreach ( $forms as $alias ) {
                                        $aliases[$alias] = $index;
@@ -740,7 +767,7 @@ class Language {
                if ( $usemsg && wfMessage( $msg )->exists() ) {
                        return $this->getMessageFromDB( $msg );
                }
-               $name = $this->langNameUtils->getLanguageName( $code );
+               $name = self::fetchLanguageName( $code );
                if ( $name ) {
                        return $name; # if it's defined as a language name, show that
                } else {
@@ -753,21 +780,21 @@ class Language {
         * @return string[]|bool List of date format preference keys, or false if disabled.
         */
        public function getDatePreferences() {
-               return $this->localisationCache->getItem( $this->mCode, 'datePreferences' );
+               return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
        }
 
        /**
         * @return array
         */
        function getDateFormats() {
-               return $this->localisationCache->getItem( $this->mCode, 'dateFormats' );
+               return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
        }
 
        /**
         * @return array|string
         */
        public function getDefaultDateFormat() {
-               $df = $this->localisationCache->getItem( $this->mCode, 'defaultDateFormat' );
+               $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
                if ( $df === 'dmy or mdy' ) {
                        global $wgAmericanDates;
                        return $wgAmericanDates ? 'mdy' : 'dmy';
@@ -780,7 +807,7 @@ class Language {
         * @return array
         */
        public function getDatePreferenceMigrationMap() {
-               return $this->localisationCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
+               return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
        }
 
        /**
@@ -801,8 +828,6 @@ class Language {
 
        /**
         * Get an array of language names, indexed by code.
-        *
-        * @deprecated since 1.34, use LanguageNameUtils::getLanguageNames
         * @param null|string $inLanguage Code of language in which to return the names
         *              Use self::AS_AUTONYMS for autonyms (native names)
         * @param string $include One of:
@@ -813,12 +838,95 @@ class Language {
         * @since 1.20
         */
        public static function fetchLanguageNames( $inLanguage = self::AS_AUTONYMS, $include = 'mw' ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->getLanguageNames( $inLanguage, $include );
+               $cacheKey = $inLanguage === self::AS_AUTONYMS ? 'null' : $inLanguage;
+               $cacheKey .= ":$include";
+               if ( self::$languageNameCache === null ) {
+                       self::$languageNameCache = new HashBagOStuff( [ 'maxKeys' => 20 ] );
+               }
+
+               $ret = self::$languageNameCache->get( $cacheKey );
+               if ( !$ret ) {
+                       $ret = self::fetchLanguageNamesUncached( $inLanguage, $include );
+                       self::$languageNameCache->set( $cacheKey, $ret );
+               }
+               return $ret;
+       }
+
+       /**
+        * Uncached helper for fetchLanguageNames
+        * @param null|string $inLanguage Code of language in which to return the names
+        *              Use self::AS_AUTONYMS for autonyms (native names)
+        * @param string $include One of:
+        *              self::ALL all available languages
+        *              'mw' only if the language is defined in MediaWiki or wgExtraLanguageNames (default)
+        *              self::SUPPORTED only if the language is in 'mw' *and* has a message file
+        * @return array Language code => language name (sorted by key)
+        */
+       private static function fetchLanguageNamesUncached(
+               $inLanguage = self::AS_AUTONYMS,
+               $include = 'mw'
+       ) {
+               global $wgExtraLanguageNames, $wgUsePigLatinVariant;
+
+               // If passed an invalid language code to use, fallback to en
+               if ( $inLanguage !== self::AS_AUTONYMS && !self::isValidCode( $inLanguage ) ) {
+                       $inLanguage = 'en';
+               }
+
+               $names = [];
+
+               if ( $inLanguage ) {
+                       # TODO: also include when $inLanguage is null, when this code is more efficient
+                       Hooks::run( 'LanguageGetTranslatedLanguageNames', [ &$names, $inLanguage ] );
+               }
+
+               $mwNames = $wgExtraLanguageNames + MediaWiki\Languages\Data\Names::$names;
+               if ( $wgUsePigLatinVariant ) {
+                       // Pig Latin (for variant development)
+                       $mwNames['en-x-piglatin'] = 'Igpay Atinlay';
+               }
+
+               foreach ( $mwNames as $mwCode => $mwName ) {
+                       # - Prefer own MediaWiki native name when not using the hook
+                       # - For other names just add if not added through the hook
+                       if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
+                               $names[$mwCode] = $mwName;
+                       }
+               }
+
+               if ( $include === self::ALL ) {
+                       ksort( $names );
+                       return $names;
+               }
+
+               $returnMw = [];
+               $coreCodes = array_keys( $mwNames );
+               foreach ( $coreCodes as $coreCode ) {
+                       $returnMw[$coreCode] = $names[$coreCode];
+               }
+
+               if ( $include === self::SUPPORTED ) {
+                       $namesMwFile = [];
+                       # We do this using a foreach over the codes instead of a directory
+                       # loop so that messages files in extensions will work correctly.
+                       foreach ( $returnMw as $code => $value ) {
+                               if ( is_readable( self::getMessagesFileName( $code ) )
+                                       || is_readable( self::getJsonMessagesFileName( $code ) )
+                               ) {
+                                       $namesMwFile[$code] = $names[$code];
+                               }
+                       }
+
+                       ksort( $namesMwFile );
+                       return $namesMwFile;
+               }
+
+               ksort( $returnMw );
+               # 'mw' option; default if it's not one of the other two options (all/mwfile)
+               return $returnMw;
        }
 
        /**
-        * @deprecated since 1.34, use LanguageNameUtils::getLanguageName
         * @param string $code The code of the language for which to get the name
         * @param null|string $inLanguage Code of language in which to return the name
         *   (SELF::AS_AUTONYMS for autonyms)
@@ -831,8 +939,9 @@ class Language {
                $inLanguage = self::AS_AUTONYMS,
                $include = self::ALL
        ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->getLanguageName( $code, $inLanguage, $include );
+               $code = strtolower( $code );
+               $array = self::fetchLanguageNames( $inLanguage, $include );
+               return !array_key_exists( $code, $array ) ? '' : $array[$code];
        }
 
        /**
@@ -2165,8 +2274,7 @@ class Language {
                }
 
                if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
-                       $df =
-                               $this->localisationCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
+                       $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
 
                        if ( $type === 'pretty' && $df === null ) {
                                $df = $this->getDateFormatString( 'date', $pref );
@@ -2174,8 +2282,7 @@ class Language {
 
                        if ( !$wasDefault && $df === null ) {
                                $pref = $this->getDefaultDateFormat();
-                               $df = $this->getLocalisationCache()
-                                       ->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
+                               $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
                        }
 
                        $this->dateFormatStrings[$type][$pref] = $df;
@@ -2539,14 +2646,14 @@ class Language {
         * @return string|null
         */
        public function getMessage( $key ) {
-               return $this->localisationCache->getSubitem( $this->mCode, 'messages', $key );
+               return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
        }
 
        /**
         * @return array
         */
        function getAllMessages() {
-               return $this->localisationCache->getItem( $this->mCode, 'messages' );
+               return self::$dataCache->getItem( $this->mCode, 'messages' );
        }
 
        /**
@@ -2788,7 +2895,7 @@ class Language {
         * @return string
         */
        function fallback8bitEncoding() {
-               return $this->localisationCache->getItem( $this->mCode, 'fallback8bitEncoding' );
+               return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
        }
 
        /**
@@ -2978,7 +3085,7 @@ class Language {
         * @return bool
         */
        function isRTL() {
-               return $this->localisationCache->getItem( $this->mCode, 'rtl' );
+               return self::$dataCache->getItem( $this->mCode, 'rtl' );
        }
 
        /**
@@ -3054,7 +3161,7 @@ class Language {
         * @return array
         */
        function capitalizeAllNouns() {
-               return $this->localisationCache->getItem( $this->mCode, 'capitalizeAllNouns' );
+               return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
        }
 
        /**
@@ -3087,7 +3194,7 @@ class Language {
         * @return bool
         */
        function linkPrefixExtension() {
-               return $this->localisationCache->getItem( $this->mCode, 'linkPrefixExtension' );
+               return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
        }
 
        /**
@@ -3095,7 +3202,7 @@ class Language {
         * @return array
         */
        function getMagicWords() {
-               return $this->localisationCache->getItem( $this->mCode, 'magicWords' );
+               return self::$dataCache->getItem( $this->mCode, 'magicWords' );
        }
 
        /**
@@ -3105,7 +3212,7 @@ class Language {
         */
        function getMagic( $mw ) {
                $rawEntry = $this->mMagicExtensions[$mw->mId] ??
-                       $this->localisationCache->getSubitem( $this->mCode, 'magicWords', $mw->mId );
+                       self::$dataCache->getSubitem( $this->mCode, 'magicWords', $mw->mId );
 
                if ( !is_array( $rawEntry ) ) {
                        wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
@@ -3140,7 +3247,7 @@ class Language {
                if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
                        // Initialise array
                        $this->mExtendedSpecialPageAliases =
-                               $this->localisationCache->getItem( $this->mCode, 'specialPageAliases' );
+                               self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
                }
 
                return $this->mExtendedSpecialPageAliases;
@@ -3305,28 +3412,28 @@ class Language {
         * @return string
         */
        function digitGroupingPattern() {
-               return $this->localisationCache->getItem( $this->mCode, 'digitGroupingPattern' );
+               return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
        }
 
        /**
         * @return array
         */
        function digitTransformTable() {
-               return $this->localisationCache->getItem( $this->mCode, 'digitTransformTable' );
+               return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
        }
 
        /**
         * @return array
         */
        function separatorTransformTable() {
-               return $this->localisationCache->getItem( $this->mCode, 'separatorTransformTable' );
+               return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
        }
 
        /**
         * @return int|null
         */
        function minimumGroupingDigits() {
-               return $this->localisationCache->getItem( $this->mCode, 'minimumGroupingDigits' );
+               return self::$dataCache->getItem( $this->mCode, 'minimumGroupingDigits' );
        }
 
        /**
@@ -4226,7 +4333,7 @@ class Language {
         * @return string
         */
        public function linkTrail() {
-               return $this->localisationCache->getItem( $this->mCode, 'linkTrail' );
+               return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
        }
 
        /**
@@ -4236,7 +4343,7 @@ class Language {
         * @return string
         */
        public function linkPrefixCharset() {
-               return $this->localisationCache->getItem( $this->mCode, 'linkPrefixCharset' );
+               return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
        }
 
        /**
@@ -4338,8 +4445,6 @@ class Language {
 
        /**
         * Get the name of a file for a certain language code
-        *
-        * @deprecated since 1.34, use LanguageNameUtils
         * @param string $prefix Prepend this to the filename
         * @param string $code Language code
         * @param string $suffix Append this to the filename
@@ -4347,30 +4452,38 @@ class Language {
         * @return string $prefix . $mangledCode . $suffix
         */
        public static function getFileName( $prefix, $code, $suffix = '.php' ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->getFileName( $prefix, $code, $suffix );
+               if ( !self::isValidBuiltInCode( $code ) ) {
+                       throw new MWException( "Invalid language code \"$code\"" );
+               }
+
+               return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
        }
 
        /**
-        * @deprecated since 1.34, use LanguageNameUtils
         * @param string $code
         * @return string
         */
        public static function getMessagesFileName( $code ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->getMessagesFileName( $code );
+               global $IP;
+               $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
+               Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
+               return $file;
        }
 
        /**
-        * @deprecated since 1.34, use LanguageNameUtils
         * @param string $code
         * @return string
         * @throws MWException
         * @since 1.23
         */
        public static function getJsonMessagesFileName( $code ) {
-               return MediaWikiServices::getInstance()->getLanguageNameUtils()
-                       ->getJsonMessagesFileName( $code );
+               global $IP;
+
+               if ( !self::isValidBuiltInCode( $code ) ) {
+                       throw new MWException( "Invalid language code \"$code\"" );
+               }
+
+               return "$IP/languages/i18n/$code.json";
        }
 
        /**
@@ -4820,13 +4933,11 @@ class Language {
         * @return array Associative array with plural form, and plural rule as key-value pairs
         */
        public function getCompiledPluralRules() {
-               $pluralRules =
-                       $this->localisationCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
+               $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
                $fallbacks = self::getFallbacksFor( $this->mCode );
                if ( !$pluralRules ) {
                        foreach ( $fallbacks as $fallbackCode ) {
-                               $pluralRules = $this->localisationCache
-                                       ->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
+                               $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
                                if ( $pluralRules ) {
                                        break;
                                }
@@ -4841,13 +4952,11 @@ class Language {
         * @return array Associative array with plural form number and plural rule as key-value pairs
         */
        public function getPluralRules() {
-               $pluralRules =
-                       $this->localisationCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
+               $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
                $fallbacks = self::getFallbacksFor( $this->mCode );
                if ( !$pluralRules ) {
                        foreach ( $fallbacks as $fallbackCode ) {
-                               $pluralRules = $this->localisationCache
-                                       ->getItem( strtolower( $fallbackCode ), 'pluralRules' );
+                               $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
                                if ( $pluralRules ) {
                                        break;
                                }
@@ -4862,13 +4971,11 @@ class Language {
         * @return array Associative array with plural form number and plural rule type as key-value pairs
         */
        public function getPluralRuleTypes() {
-               $pluralRuleTypes =
-                       $this->localisationCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
+               $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
                $fallbacks = self::getFallbacksFor( $this->mCode );
                if ( !$pluralRuleTypes ) {
                        foreach ( $fallbacks as $fallbackCode ) {
-                               $pluralRuleTypes = $this->localisationCache
-                                       ->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
+                               $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
                                if ( $pluralRuleTypes ) {
                                        break;
                                }
index 1d80f6b..00f35b2 100644 (file)
@@ -39,7 +39,7 @@ namespace MediaWiki\Languages\Data;
  * If you are adding support for such a language, add it also to
  * the relevant section in shared.css.
  *
- * Do not use this class directly. Use LanguageNameUtils::getLanguageNames(), which
+ * Do not use this class directly. Use Language::fetchLanguageNames(), which
  * includes support for the CLDR extension.
  *
  * @ingroup Language
index bfaac9d..595ba7e 100644 (file)
@@ -47,7 +47,7 @@
        "tog-fancysig": "امضاءَ په داب ویکی متنی بزان(بی اتوماتیکی لینک)",
        "tog-uselivepreview": "پیش‌نمایش بدون نیاز به بروزرسانی صفحه",
        "tog-forceeditsummary": "من آ هال دی وهدی وارد کتن یک هالیکین خلاصه ی اصلاح",
-       "tog-watchlisthideown": "منی اصلاحات آ چه لیست چارگ پناه کن",
+       "tog-watchlisthideown": "منی ٹگلان چہ چارگء لیست‌ئا پناہ کن",
        "tog-watchlisthidebots": "اصلاحات بوت چه لیست چارگ پناه کن",
        "tog-watchlisthideminor": "هوردین اصلاحات چه لیست چارگ پناه کن",
        "tog-watchlisthideliu": "اصلاحات چه وارد بوتگین کاربران چه لیست چارگان پناه کن",
        "mimetype": "نوع مایم:",
        "download": "آیرگیزگ",
        "unwatchedpages": "نه چارتگین صفحات",
-       "listredirects": "Ù\84Û\8cست ØºÛ\8cر Ù\85ستÙ\82Û\8cÙ\85ان",
+       "listredirects": "Ù\86اتÙ\90Ú\86Ú©Ý\94Úº Ù\84Û\8cستان",
        "listduplicatedfiles": "فهرست همهٔ پرونده‌ها به‌همراه تکراری‌ها",
        "listduplicatedfiles-summary": "این فهرست پرونده‌هایی با نسخه‌های اخیر این پرونده تکراری است که نسخه‌های اخبر سایر پرونده‌ها است. فقط پرونده‌های محلی در نظر گرفته شده‌اند.",
        "listduplicatedfiles-entry": "[[:File:$1|$1]][[$3|{{PLURAL:$2|یک تکرار|$2 تکرار}}]] دارد.",
        "listusers-submit": "پیش دار",
        "listusers-noresult": "هچ کابری در گیزگ نه بوت.",
        "listusers-blocked": "(بند بیتگ)",
-       "activeusers": "لیست کاربران فعال",
+       "activeusers": "کنشدارݔں کارزورۏکانء لیست",
        "activeusers-count": "$1 {{PLURAL:$1|اصلاح|اصلاح}} نوکین",
        "activeusers-from": "پیشدار کاربرانی که شروع بنت گون :‌",
        "activeusers-noresult": "هچ کاربری درگیزگ نه بیت",
        "listgrouprights-group": "گروه",
        "listgrouprights-rights": "حقوق",
        "listgrouprights-helppage": "Help: حقوق گروه",
-       "listgrouprights-members": "(لیست اعضا)",
+       "listgrouprights-members": "(ھۏرݔنانء لیست)",
        "listgrouprights-addgroup": "تونیت اضافه کنت {{PLURAL:$2|گروه|گروهان}}: $1",
        "listgrouprights-removegroup": "تونیت بزوریت {{PLURAL:$2|گروهء|گروهانء}}: $1",
        "listgrouprights-addgroup-all": "تونیت کل گروهان اضافه کنت",
index 821a48c..954ba46 100644 (file)
        "exif-photometricinterpretation-3": "Paletă",
        "exif-photometricinterpretation-4": "Mască de transparență",
        "exif-photometricinterpretation-5": "Separat (Probabil CMYK)",
+       "exif-photometricinterpretation-8": "CIE L*a*b*",
+       "exif-photometricinterpretation-9": "CIE L*a*b* (codare ICC)",
+       "exif-photometricinterpretation-10": "CIE L*a*b* (codare ITU)",
        "exif-unknowndate": "Dată necunoscută",
        "exif-orientation-1": "Normală",
        "exif-orientation-2": "Oglindită orizontal",
index e7608a6..473a03a 100644 (file)
        "exif-bitspersample": "Bitůw na průbka",
        "exif-compression": "Metoda kompresyji",
        "exif-photometricinterpretation": "Interpretacyjo fotůmetryčno",
-       "exif-orientation": "Uorjyntacyjo uobrozu",
+       "exif-orientation": "Ôriyntacyjŏ",
        "exif-samplesperpixel": "Průbek na piksel",
        "exif-planarconfiguration": "Rozkuod danych",
        "exif-ycbcrsubsampling": "Podprůbkowańe Y do C",
        "exif-ycbcrpositioning": "Rozmješčyńy Y i C",
-       "exif-xresolution": "Rozdźelčość w poźůmje",
-       "exif-yresolution": "Rozdźelčość w pjůńy",
+       "exif-xresolution": "Rozdzielczość we poziōmie",
+       "exif-yresolution": "Rodzielczość we piōnie",
        "exif-stripoffsets": "Přesůńjyńće pasůw uobrazu",
        "exif-rowsperstrip": "Ličba wjeršy na pas uobrazu",
        "exif-stripbytecounts": "Ličba bajtůw na pas uobrazu",
        "exif-primarychromaticities": "Kolory třech barw guůwnych",
        "exif-ycbcrcoefficients": "Maćeř wspůučynńikůw transformacyji barw ze RGB na YCbCr",
        "exif-referenceblackwhite": "Wartość půnktu uodńyśyńo čerńi i bjeli",
-       "exif-datetime": "Data i čas modyfikacyji plika",
+       "exif-datetime": "Data i czas modyfikacyje zbioru",
        "exif-imagedescription": "Titel uobrozka",
        "exif-make": "Producynt fotoaparatu",
        "exif-model": "Model fotoaparatu",
-       "exif-software": "Ůžyte uoprůgramowańy",
+       "exif-software": "Użyte ôprogramowanie",
        "exif-artist": "Autor",
        "exif-copyright": "Wuaśćićel praw autorskych",
-       "exif-exifversion": "Wersyja standardu Exif",
+       "exif-exifversion": "Wersyjŏ Exif",
        "exif-flashpixversion": "Uobsůgiwano wersyjo Flashpix",
-       "exif-colorspace": "Přestřyń kolorůw",
+       "exif-colorspace": "Przestrzyń farbōw",
        "exif-componentsconfiguration": "Značyńy skuadowych",
        "exif-compressedbitsperpixel": "Skůmpresowanych bitůw na piksel",
        "exif-pixelxdimension": "Prawidłowa szyrzka uobrozu",
        "exif-pixelydimension": "Prawidłowo wyżka uobrozu",
        "exif-usercomment": "Kůmyntoř užytkowńika",
        "exif-relatedsoundfile": "Powjůnzany plik audjo",
-       "exif-datetimeoriginal": "Data i čas utwořyńo uoryginouu",
-       "exif-datetimedigitized": "Data i čas zeskanowańo",
+       "exif-datetimeoriginal": "Data i czas stworzyniŏ ôryginału",
+       "exif-datetimedigitized": "Data i czas digitalizacyje",
        "exif-subsectime": "Data i čas modyfikacyji pliku – uuamki sekůnd",
        "exif-subsectimeoriginal": "Data i čas utwořyńo uoryginouu – uuamki sekůnd",
        "exif-subsectimedigitized": "Data i čas zeskanowańo – uuamki sekůnd",
        "exif-gpsdifferential": "Korekcyjo růžńicy GPS",
        "exif-compression-1": "ńyskůmpresowany",
        "exif-unknowndate": "ńyznano data",
-       "exif-orientation-1": "normalno",
+       "exif-orientation-1": "Normalno",
        "exif-orientation-2": "odbiće we źřadle w poźůmje",
        "exif-orientation-3": "uobroz uobrůcůny uo 180°",
        "exif-orientation-4": "uodbiće we źřadle w pjůńy",
index 2cc372a..5752bee 100644 (file)
@@ -16,6 +16,8 @@
        "exif-samplesperpixel": "Төс өлешләре саны",
        "exif-xresolution": "Ятма ачыклык",
        "exif-yresolution": "Асма ачыклык",
+       "exif-rowsperstrip": "Бер бүлемдә юллар саны",
+       "exif-stripbytecounts": "Кысылган бүлемдә байтлар саны",
        "exif-datetime": "Файл үзгәреше датасы һәм вакыты",
        "exif-imagedescription": "Сурәт атамасы",
        "exif-make": "Камера җитештерүчесе",
@@ -23,7 +25,7 @@
        "exif-software": "Кулланылган программа",
        "exif-artist": "Автор",
        "exif-copyright": "Авторлык хокукы иясе",
-       "exif-exifversion": "Exif Ñ\8eÑ\80амаÑ\81ы",
+       "exif-exifversion": "Exif Ñ\87Ñ\8bгаÑ\80Ñ\8bÑ\88ы",
        "exif-flashpixversion": "FlashPix ярашлы юрамасы",
        "exif-colorspace": "Төсләр киңлеге",
        "exif-componentsconfiguration": "Төсләр төзелешенең конфигурациясе",
        "exif-gpslongitude": "Озынлык",
        "exif-gpsaltituderef": "Югарылык индексы",
        "exif-gpsaltitude": "Югарылык",
-       "exif-gpstimestamp": "UTC буенча вакыт",
+       "exif-gpstimestamp": "GPS вакыты (атом сәгате)",
        "exif-gpssatellites": "Кулланылган иярченнәр тасвирламасы",
        "exif-gpsstatus": "Алгычның статусы һәм төшерү вакыты",
        "exif-gpsmeasuremode": "Урнашуны билгеләү ысулы",
        "exif-gpsdop": "Билгеләүнең дөреслеге",
        "exif-gpsspeedref": "Тизлекне исәпләү берәмлеге",
        "exif-gpsspeed": "Хәрәкәт тизлеге",
-       "exif-gpsdatestamp": "Дата",
+       "exif-gpsdatestamp": "GPS датасы",
        "exif-keywords": "Иң мөһиме",
+       "exif-headline": "Башисем",
        "exif-source": "Чыганак",
+       "exif-contact": "Элемтә өчен мәгълүмат",
        "exif-writer": "Язучы",
        "exif-languagecode": "Тел",
        "exif-iimversion": "IIM юрамасы",
        "exif-iimcategory": "Төркем",
        "exif-iimsupplementalcategory": "Өстәмә төркемнәр",
+       "exif-datetimereleased": "Чыгарылу вакыты",
        "exif-identifier": "Идентификатор",
        "exif-label": "Билгеләү",
        "exif-copyrighted": "Авторлык хокукы халәте",
        "exif-copyrightowner": "Авторлык хокукы иясе",
        "exif-usageterms": "Куллану шартлары",
+       "exif-photometricinterpretation-0": "Ак һәм кара (ак — 0)",
+       "exif-photometricinterpretation-1": "Ак һәм кара (кара — 0)",
+       "exif-unknowndate": "Билгесез вакыт",
        "exif-orientation-1": "Гадәти",
        "exif-orientation-3": "180° ка борылган",
+       "exif-planarconfiguration-1": "«chunky» форматы",
+       "exif-planarconfiguration-2": "«planar» форматы",
        "exif-componentsconfiguration-0": "барлыкта юк",
        "exif-exposureprogram-0": "Билгесез",
        "exif-exposureprogram-1": "Кулдан җайлау режимы",
        "exif-meteringmode-0": "Билгесез",
        "exif-meteringmode-1": "Уртача",
        "exif-meteringmode-3": "Нокталы",
-       "exif-meteringmode-4": "Ð\9cÑ\83лÑ\8cÑ\82инокталы",
+       "exif-meteringmode-4": "Ð\9aүп нокталы",
        "exif-meteringmode-5": "Паттернлы",
        "exif-meteringmode-6": "Өлешләтә",
        "exif-meteringmode-255": "Башка",
        "exif-gpsdop-moderate": "Уртача ($1)",
        "exif-gpsdop-fair": "Ярыйсы ($1)",
        "exif-gpsdop-poor": "Начар ($1)",
+       "exif-objectcycle-a": "Иртән генә",
+       "exif-objectcycle-p": "Кичен генә",
+       "exif-objectcycle-b": "Иртән һәм кичен",
        "exif-dc-date": "Дата(лар)",
        "exif-dc-publisher": "Нәшир",
        "exif-dc-relation": "Бәйле медиа",
        "exif-dc-type": "Медиа төре",
        "exif-rating-rejected": "Кире кагылды",
        "exif-isospeedratings-overflow": "65535 тән күбрәк",
+       "exif-iimcategory-fin": "Экономика һәм бизнес",
+       "exif-iimcategory-evn": "Әйләнә-тирәдәге мохит",
        "exif-iimcategory-hth": "Сәламәтлек",
        "exif-iimcategory-lab": "Хезмәт",
+       "exif-iimcategory-pol": "Сәясәт",
+       "exif-iimcategory-rel": "Дин һәм иман",
+       "exif-iimcategory-sci": "Фән һәм техника",
+       "exif-iimcategory-spo": "Спорт",
        "exif-iimcategory-wea": "Һава торышы",
        "exif-urgency-normal": "Гадәти ($1)",
        "exif-urgency-low": "Түбән ($1)",
index 0a03da6..b0aced5 100644 (file)
        "revdelete-unsuppress": "حذف محدودیت‌ها در بازبینی‌های ترمیم‌شده",
        "revdelete-log": "دلیل:",
        "revdelete-submit": "اعمال بر {{PLURAL:$1|نسخهٔ|نسخه‌های}} انتخاب شده",
-       "revdelete-success": "Ù¾Û\8cداÛ\8cÛ\8c Ù\86سخÙ\87 Ø¨Ù\87â\80\8cرÙ\88ز شد.",
+       "revdelete-success": "Ù¾Û\8cداÛ\8cÛ\8c Ù\86سخÙ\87 Ø±Ù\88زآÙ\85د شد.",
        "revdelete-failure": "'''پیدایی نسخه‌ها قابل به روز کردن نیست:'''\n$1",
        "logdelete-success": "تغییر پیدایی مورد انجام شد.",
        "logdelete-failure": "'''پیدایی سیاهه‌ها قابل تنظیم نیست:'''\n$1",
index 8b8a973..aca6e7c 100644 (file)
        "tag-mw-contentmodelchange": "Modifiko di la kontenajo di ula modelo",
        "tag-mw-contentmodelchange-description": "Redakturi qui [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel modifikas la modelo di kontenajo] di ula pagino",
        "tag-mw-new-redirect": "Nova ridirekto",
+       "tag-mw-new-redirect-description": "Redakturi qui kreas nova ridirekto, o chanjas kontenajo di pagino a ridirekto",
+       "tag-mw-removed-redirect": "Ridirekto efacita",
+       "tag-mw-changed-redirect-target": "Emo di ridirekto modifikata",
+       "tag-mw-changed-redirect-target-description": "Redakturi qui modifikas la skopo di ula ridirekto",
        "tag-mw-blank-description": "Redakturi qui efacas pagini",
        "tag-mw-replace": "Remplasita",
        "tag-mw-replace-description": "Redakturi qui removas plua kam 90% de la kontenajo di ula pagino",
index 26532b6..7feae60 100644 (file)
                        "Senpremì",
                        "Ignazio Cannata",
                        "Frubino",
-                       "TheRukk"
+                       "TheRukk",
+                       "Titore"
                ]
        },
        "tog-underline": "Sottolinea i collegamenti:",
        "blockedtitle": "Utente bloccato.",
        "blocked-email-user": "<strong>Alla tua utenza è stato vietato l'invio di email. Puoi ancora modificare altre pagine di questa wiki.</strong> Puoi vedere tutti i dettagli del blocco su [[Special:MyContributions|contributi dell'utenza]].\n\nIl blocco è stato effettuato da $1.\n\nLa ragione data è <em>$2</em>.\n\n* Inizio del blocco: $8\n* Scadenza del blocco: $6\n* Destinatario del blocco: $7\n* ID Blocco #$5",
        "blockedtext-partial": "<strong>Alla tua utenza o indirizzo IP è stato vietato di apportare modifiche a questa pagina. Puoi ancora modificare altre pagine di questa wiki.</strong> Puoi vedere tutti i dettagli del blocco su [[Special:MyContributions|contributi dell'utenza]].\n\nIl blocco è stato effettuato da $1.\n\nLa ragione data è <em>$2</em>.\n\n* Inizio del blocco: $8\n* Scadenza del blocco: $6\n* Destinatario del blocco: $7\n* ID Blocco #$5",
-       "blockedtext": "<strong>Il tuo nome utente o indirizzo IP è stato bloccato.</strong>\n\nIl blocco è stato imposto da $1. La motivazione del blocco è la seguente: <em>$2</em>.\n\n* Inizio del blocco: $8\n* Scadenza del blocco: $6\n* Intervallo di blocco: $7\n\nSe lo si desidera, è possibile contattare $1 o un altro [[{{MediaWiki:Grouppage-sysop}}|amministratore]] per discutere del blocco.\n\nSi noti che la funzione \"{{int:emailuser}}\" non è attiva se non è stato registrato un indirizzo email valido nelle proprie [[Special:Preferences|preferenze]] o se l'utilizzo di tale funzione è stato bloccato.\n\nL'indirizzo IP attuale è $3, il numero ID del blocco è #$5.\nSi prega di specificare tutti i dettagli precedenti in qualsiasi richiesta di chiarimenti.",
+       "blockedtext": "<strong>Il tuo nome utente o indirizzo IP è stato bloccato.</strong>\n\nIl blocco è stato imposto da $1. La motivazione del blocco è la seguente: <em>$2</em>.\n\n* Inizio del blocco: $8\n* Scadenza del blocco: $6\n* Destinatario del blocco: $7\n\nSe lo si desidera, è possibile contattare $1 o un altro [[{{MediaWiki:Grouppage-sysop}}|amministratore]] per discutere del blocco.\n\nSi noti che la funzione \"{{int:emailuser}}\" non è attiva se non è stato registrato un indirizzo email valido nelle proprie [[Special:Preferences|preferenze]] o se l'utilizzo di tale funzione è stato bloccato.\n\nL'indirizzo IP attuale è $3, il numero ID del blocco è #$5.\nSi prega di specificare tutti i dettagli precedenti in qualsiasi richiesta di chiarimenti.",
        "autoblockedtext": "Questo indirizzo IP è stato bloccato automaticamente perché condiviso con un altro utente, a sua volta bloccato da $1.\nLa motivazione del blocco è la seguente:\n\n:<em>$2</em>\n\n* Inizio del blocco: $8\n* Scadenza del blocco: $6\n* Intervallo di blocco: $7\n\nÈ possibile contattare $1 o un altro [[{{MediaWiki:Grouppage-sysop}}|amministratore]] per richiedere eventuali chiarimenti circa il blocco.\n\nSi noti che la funzione \"{{int:emailuser}}\" non è attiva se non è stato registrato un indirizzo e-mail valido nelle proprie [[Special:Preferences|preferenze]] e, comunque, se nell'applicare il blocco, tale funzione è stata disabilitata (per la durata del blocco).\n\nL'indirizzo IP attuale è $3, il numero ID del blocco è #$5\nSi prega di specificare tutti i dettagli qui inclusi nel compilare qualsiasi richiesta di chiarimenti.",
        "systemblockedtext": "Il tuo nome utente o l'indirizzo IP è stato bloccato automaticamente da MediaWiki.\nLa motivazione del blocco è la seguente:\n\n:''$2''\n\n* Inizio del blocco: $8\n* Scadenza del blocco: $6\n* Intervallo di blocco: $7\n\nL'indirizzo IP attuale è $3.\nSi prega di specificare tutti i dettagli qui inclusi nel compilare qualsiasi richiesta di chiarimenti.",
        "blockednoreason": "nessuna motivazione indicata",
        "last": "prec",
        "page_first": "prima",
        "page_last": "ultima",
-       "histlegend": "Confronto tra versioni: selezionare le caselle corrispondenti alle versioni desiderate e premere Invio o il pulsante in basso.\n\nLegenda: '''({{int:cur}})''' = differenze con la versione attuale, '''({{int:last}})''' = differenze con la versione precedente, '''{{int:minoreditletter}}''' = modifica minore",
+       "histlegend": "Confronto tra versioni: selezionare le caselle corrispondenti alle versioni desiderate e premere Invio o il pulsante in basso.\n\nLegenda: '''({{int:cur}})''' = differenze con la versione corrente, '''({{int:last}})''' = differenze con la versione precedente, '''{{int:minoreditletter}}''' = modifica minore",
        "history-fieldset-title": "Filtra versioni",
        "history-show-deleted": "Solo versioni cancellate",
        "histfirst": "prima",
        "prefs-timeoffset": "Ore di differenza",
        "prefs-advancedediting": "Opzioni generali",
        "prefs-developertools": "Strumenti per gli sviluppatori",
-       "prefs-editor": "Editore",
+       "prefs-editor": "Editor",
        "prefs-preview": "Anteprima",
        "prefs-advancedrc": "Opzioni avanzate",
        "prefs-advancedrendering": "Opzioni avanzate",
index d597b5e..fc6d25f 100644 (file)
        "confirm-unwatch-button": "ٺيڪ",
        "confirm-unwatch-top": "هيءُ صفحو پنهنجي نظر ۾ فهرست مان هٽائيندا؟",
        "confirm-rollback-top": "ھن صفحي ۾ ڪيل سنوارون واپس ورايون؟",
+       "semicolon-separator": "؛&#32;",
+       "comma-separator": "،&#32;",
        "quotation-marks": "\"$1\"",
        "imgmultipageprev": "← اڳوڻو صفحو",
        "imgmultipagenext": "اڳيون صفحو ←",
index f50c18c..c639a58 100644 (file)
@@ -73,6 +73,7 @@
        "tog-norollbackdiff": "రోల్‌బ్యాక్ చేసాక తేడాలు చూపించవద్దు",
        "tog-useeditwarning": "ఏదైనా పేజీని నేను వదిలివెళ్తున్నప్పుడు దానిలో భద్రపరచని మార్పులు ఉంటే నన్ను హెచ్చరించు",
        "tog-prefershttps": "లాగిన్ అయి ఉన్నప్పుడెల్లా భద్ర కనెక్షనునే వాడు",
+       "tog-showrollbackconfirmation": "రోల్‌బ్యాక్ లింకును నొక్కినపుడు నిర్ధారించుకునే సందేశాన్ని చూపించు",
        "underline-always": "ఎల్లప్పుడూ",
        "underline-never": "ఎప్పటికీ వద్దు",
        "underline-default": "అలంకారపు లేదా విహారిణి అప్రమేయం",
        "index-category": "సూచీకరించిన పేజీలు",
        "noindex-category": "సూచీకరించని పేజీలు",
        "broken-file-category": "తెగిపోయిన ఫైలులింకులు గల పేజీలు",
+       "categoryviewer-pagedlinks": "($1) ($2)",
+       "category-header-numerals": "$1–$2",
        "about": "గురించి",
        "article": "విషయపు పేజీ",
        "newwindow": "(కొత్త విండోలో వస్తుంది)",
        "versionrequired": "మీడియావికీ సాఫ్టువేరు వెర్షను $1 కావాలి",
        "versionrequiredtext": "ఈ పేజీని వాడటానికి మీకు మీడియావికీ సాఫ్టువేరు వెర్షను $1 కావాలి. [[Special:Version|వెర్షను పేజీ]]ని చూడండి.",
        "ok": "సరే",
+       "pagetitle": "$1 - {{SITENAME}}",
+       "pagetitle-view-mainpage": "{{SITENAME}}",
+       "backlinksubtitle": "← $1",
        "retrievedfrom": "\"$1\" నుండి వెలికితీశారు",
        "youhavenewmessages": "మీకు $1 ఉన్నాయి ($2).",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|మీకు}} {{PLURAL:$3|మరో వాడుకరి|$3 వాడుకరుల}} నుండి  $1 ($2).",
        "page-rss-feed": "\"$1\" RSS ఫీడు",
        "page-atom-feed": "\"$1\" ఆటమ్ ఫీడు",
        "feed-atom": "యాటమ్",
+       "feed-rss": "RSS",
        "red-link-title": "$1 (పుట లేదు)",
        "sort-descending": "అవరోహణ క్రమంలో అమర్చు",
        "sort-ascending": "ఆరోహణ క్రమంలో అమర్చు",
        "virus-scanfailed": "స్కాన్ విఫలమైంది (సంకేతం $1)",
        "virus-unknownscanner": "అజ్ఞాత యాంటీవైరస్:",
        "logouttext": "<strong>ఇప్పుడు మీరు లాగౌటయ్యారు.</strong>\n\nఅయితే, ఓ గమనిక.. మీ విహారిణిలోని కోశాన్ని ఖాళీ చేసేవరకూ కొన్ని పేజీలు మీరింకా లాగినై ఉన్నట్లుగానే చూపించవచ్చు.",
+       "logging-out-notify": "మిమ్మల్ని లాగౌటు చేస్తున్నాం, ఆగండి.",
        "logout-failed": "ఇప్పుడు లాగౌట్ అవలేరు: $1",
        "cannotlogoutnow-title": "ఇప్పుడు లాగౌట్ అవలేరు",
        "cannotlogoutnow-text": "$1 ను వాడుతూండగా లాగౌట్ అవలేరు.",
        "nocookiesnew": "ఖాతాని సృష్టించాం, కానీ మీరు ఇంకా లోనికి ప్రవేశించలేదు.\nవాడుకరుల ప్రవేశానికి {{SITENAME}} కూకీలను వాడుతుంది.\nమీరు కూకీలని అచేతనం చేసివున్నారు.\nదయచేసి వాటిని చేతనంచేసి, మీ కొత్త వాడుకరి పేరు, సంకేతపదాలతో లోనికి ప్రవేశించండి.",
        "nocookieslogin": "వాడుకరుల ప్రవేశానికై {{SITENAME}} కూకీలను వాడుతుంది.\nమీరు కుకీలని అచేతనం చేసివున్నారు.\nవాటిని చేతనంచేసి ప్రయత్నించండి.",
        "nocookiesfornew": "మూలాన్ని కనుక్కోలేకపోయాం కాబట్టి, ఈ వాడుకరి ఖాతాను సృష్టించలేకపోయాం.\nమీ కంప్యూటర్లో కూకీలు చేతనమై ఉన్నాయని నిశ్చయించుకొని, ఈ పేజీని తిరిగి లోడు చేసి, మళ్ళీ ప్రయత్నించండి.",
+       "nocookiesforlogin": "{{int:nocookieslogin}}",
        "createacct-loginerror": "ఖాతా విజయవంతంగా సృష్టించబడింది, కానీ ఆటోమాటిగ్గా లాగిన్ అవలేరు.  స్వయంగా మీరే [[Special:UserLogin|లాగినవండి]].",
        "noname": "మీరు సరైన వాడుకరి పేరు ఇవ్వలేదు.",
        "loginsuccesstitle": "లాగినయ్యారు",
        "user-mail-no-body": "ఈమెయిలును ఖాళీగానో, మరీ తక్కువ విషయంతోనో పంపేందుకు ప్రయత్నించారు.",
        "changepassword": "సంకేతపదాన్ని మార్చండి",
        "resetpass_announce": "లాగిన్ను పూర్తిచేసేందుకు, తప్పనిసరిగా కొత్త సంకేతపదాన్ని ఇవ్వాలి:",
+       "resetpass_text": "<!-- ఇక్కడ పాఠ్యం చేర్చండి  -->",
        "resetpass_header": "ఖాతా సంకేతపదం మార్పు",
        "oldpassword": "పాత సంకేతపదం:",
        "newpassword": "కొత్త సంకేతపదం:",
        "headline_tip": "2వ స్థాయి శీర్షిక",
        "nowiki_sample": "ఫార్మాటు చేయని పాఠ్యాన్ని ఇక్కడ చేర్చండి",
        "nowiki_tip": "వికీ ఫార్మాటును పట్టించుకోవద్దు",
+       "image_sample": "Example.jpg",
        "image_tip": "ఇమిడ్చిన ఫైలు",
+       "media_sample": "Example.ogg",
        "media_tip": "దస్త్రపు లంకె",
        "sig_tip": "సమయంతో సహా మీ సంతకం",
        "hr_tip": "అడ్డగీత (అరుదుగా వాడండి)",
        "template-protected": "(సంరక్షితం)",
        "template-semiprotected": "(సెమీ-రక్షణలో ఉంది)",
        "hiddencategories": "ఈ పేజీ {{PLURAL:$1|ఒక దాచిన వర్గంలో|$1 దాచిన వర్గాల్లో}} ఉంది:",
+       "edittools-upload": "-",
        "nocreatetext": "{{SITENAME}}లో కొత్త పేజీలు సృష్టించడాన్ని నియంత్రించారు.\nమీరు వెనక్కి వెళ్ళి వేరే పేజీలు మార్చవచ్చు, లేదా [[Special:UserLogin|లోనికి ప్రవేశించండి లేదా ఖాతా సృష్టించుకోండి]].",
        "nocreate-loggedin": "కొత్త పేజీలను సృష్టించేందుకు మీకు అనుమతి లేదు.",
        "sectioneditnotsupported-title": "విభాగపు దిద్దుబాట్లకు తోడ్పాటు లేదు",
        "content-not-allowed-here": "స్లాట్ \"$3\" లో [[:$2]] పేజీలో పాఠ్యం \"$1\" కి అనుమతి లేదు",
        "editwarning-warning": "ఈ పేజీని వదిలివెళ్ళడం వల్ల మీరు చేసిన మార్పులను కోల్పోయే అవకాశం ఉంది.\nమీరు లాగిన్ అయివుంటే, ఈ హెచ్చరికని మీ అభిరుచులలోని \"{{int:prefs-editing}}\"  విభాగంలో అచేతనం చేసుకోవచ్చు.",
        "editpage-invalidcontentmodel-title": "ఈ కంటెంటు మోడలుకు మద్దతు లేదు",
+       "editpage-invalidcontentmodel-text": "\"$1\" అనే కంటెంటు మోడలుకు మద్దతు లేదు.",
        "editpage-notsupportedcontentformat-title": "పాఠ్యపు ఆకృతికి మద్దతు లేదు",
        "editpage-notsupportedcontentformat-text": "$2 పాఠ్యపు మోడల్, పాఠ్యపు ఆకృతి $1 కి మద్దతు ఇవ్వదు",
        "slot-name-main": "ప్రధాన",
        "content-model-text": "సాదా పాఠ్యం",
        "content-model-javascript": "జావాస్క్రిప్ట్",
        "content-model-css": "CSS",
+       "content-model-json": "JSON",
        "content-json-empty-object": "ఖాళీ అంశం",
        "content-json-empty-array": "ఖాళీ అరే",
        "duplicate-args-warning": "<strong>హెచ్చరిక:</strong> [[:$1]], \"$3\" పరామితికి ఒకటి కంటే ఎక్కువ విలువలు ఇచ్చి [[:$2]] ను పిలుస్తోంది. చిట్టచివరిగా ఇచ్చిన విలువను మాత్రమే వాడుతాం.",
        "mergehistory-comment": "[[:$1]]ని [[:$2]] లోనికి విలీనం చేసారు: $3",
        "mergehistory-same-destination": "మూల, గమ్యస్థాన పేజీలు ఒకటే కాకూడదు",
        "mergehistory-reason": "కారణం:",
+       "mergehistory-revisionrow": "$1 ($2) $3 . . $4 $5 $6",
        "mergelog": "విలీనాల చిట్టా",
        "revertmerge": "విలీనాన్ని రద్దుచెయ్యి",
        "mergelogpagetext": "ఒక పేజీ చరితాన్ని మరో పేజీ చరితం లోకి ఇటీవల చేసిన విలీనాల జాబితా ఇది.",
        "youremail": "ఈమెయిలు:",
        "username": "{{GENDER:$1|వాడుకరి పేరు}}:",
        "prefs-memberingroups": "ఈ {{PLURAL:$1|గుంపులో|గుంపులలో}} {{GENDER:$2|సభ్యుడు|సభ్యురాలు}}:",
+       "prefs-memberingroups-type": "$1",
        "group-membership-link-with-expiry": "$1 ($2 వరకు)",
        "prefs-registration": "నమోదైన సమయం:",
+       "prefs-registration-date-time": "$1",
        "yourrealname": "అసలు పేరు:",
        "yourlanguage": "భాష:",
        "yourvariant": "విషయపు భాషా వైవిధ్యం:",
        "prefs-advancedwatchlist": "ఉన్నత ఎంపికలు",
        "prefs-displayrc": "ప్రదర్శన ఎంపికలు",
        "prefs-displaywatchlist": "ప్రదర్శన ఎంపికలు",
+       "prefs-changesrc": "చూపించే మార్పులు",
+       "prefs-changeswatchlist": "చూపించే మార్పులు",
+       "prefs-pageswatchlist": "వీక్షించే పేజీలు",
        "prefs-tokenwatchlist": "టోకెన్",
        "prefs-diffs": "తేడాలు",
        "prefs-help-prefershttps": "ఈ అభిరుచి మీరు పైసారి లాగినైనపుడు అమలౌతుంది.",
        "saveusergroups": "{{GENDER:$1|వాడుకరి}} గుంపులను భద్రపరచు",
        "userrights-groupsmember": "సభ్యులు:",
        "userrights-groupsmember-auto": "సంభావిత సభ్యులు:",
+       "userrights-groupsmember-type": "$1",
        "userrights-groups-help": "ఈ వాడుకరి ఏయే గుంపులలో ఉండాలో మీరు మార్చవచ్చు.\n* టిక్కు పెట్టివుంటే సదరు గుంపులో ఈ వాడుకరి ఉన్నట్టు.\n* టిక్కు లేకుంటే సదరు గుంపులో ఈ వాడుకరి లేనట్టు.\n* * గుర్తు ఉంటే ఒకసారి ఆ గుంపుకు చేర్చాక మీరు తీసివేయలేరు, లేదా తీసివేసాక తిరిగి చేర్చలేరు.\n* ఈ # గుర్తు ఉంటే ఆ గుంపు కాలం తీరిపోయే సమయాన్ని పెంచగలరు; దాన్ని తగ్గించలేరు.",
        "userrights-reason": "కారణం:",
        "userrights-no-interwiki": "ఇతర వికీలలో వాడుకరి హక్కులను మార్చడానికి మీకు అనుమతి లేదు.",
        "userrights-nodatabase": "$1 అనే డేటాబేసు లేదు లేదా అది స్థానికం కాదు.",
        "userrights-changeable-col": "మీరు మార్చదగిన గుంపులు",
        "userrights-unchangeable-col": "మీరు మార్చలేని గుంపులు",
+       "userrights-irreversible-marker": "$1*",
+       "userrights-no-shorten-expiry-marker": "$1#",
        "userrights-expiry-current": "కాలంతీరే వ్యవధి $1",
        "userrights-expiry-none": "ఎన్నటికీ కాలం తీరిపోదు",
        "userrights-expiry": "కాలం తీరిపోయే వ్యవధి",
        "action-applychangetags": "మీ మార్పులతో ట్యాగులను ఆపాదించే",
        "action-deletechangetags": "డేటాబేసు నుండి ట్యాగులను తొలగించే",
        "action-purge": "ఈ పేజీని పర్జ్ చేసే",
+       "action-bigdelete": "పెద్ద చరితం ఉన్న పేజీలను తొలగించు",
        "action-blockemail": "ఈమెయిలు పంపకుండా వాడుకరిని నిరోధించే",
        "action-bot": "ఆటోమాటిక్ ప్రాసెస్ లాగా భావించే",
        "action-editsemiprotected": "\"{{int:protect-level-autoconfirmed}}\" గా సంరక్షించబడ్డ పేజీలను మార్చే",
        "action-override-export-depth": "5 లింకుల లోతు వరకు ఉన్న పేజీలతో సహా, పేజీలను ఎగుమతి చేసే",
        "action-suppressredirect": "పేజీని తరలించేటపుడు పాత పేరు నుండి దారిమార్పును సృష్టించకుండా చేసే",
        "nchanges": "{{PLURAL:$1|ఒక మార్పు|$1 మార్పులు}}",
+       "ntimes": "$1×",
        "enhancedrc-since-last-visit": "{{PLURAL:$1|చివరి సందర్శన తరువాత}}, $1",
        "enhancedrc-history": "చరిత్ర",
        "recentchanges": "ఇటీవలి మార్పులు",
        "recentchanges-label-plusminus": "ఈ పేజి పరిమాణంలో  జరిగిన మార్పుల  బైట్ల సంఖ్య",
        "recentchanges-legend-heading": "<strong>సూచిక :</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|కొత్త పేజీల జాబితా]]ను కూడా చూడండి)",
+       "recentchanges-legend-plusminus": "(<em>±123</em>)",
        "recentchanges-submit": "చూపించు",
        "rcfilters-tag-remove": "'$1'ను తీసివెయ్యి",
        "rcfilters-legend-heading": "<strong>సంక్షేపాల (ఎబ్రీవియేషన్లు) జాబితా:</strong>",
        "rcfilters-clear-all-filters": "వడపోతకాలన్నింటినీ తుడిచెయ్యి",
        "rcfilters-show-new-changes": "$1 నుండి జరిగిన సరికొత్త మార్పులను చూడండి",
        "rcfilters-search-placeholder": "మార్పులను వడకట్టండి (మెనూను వాడండి లేదా వడపోత పేరు కోసం వెతకండి)",
+       "rcfilters-search-placeholder-mobile": "వడపోతలు",
        "rcfilters-invalid-filter": "తప్పు వడపోతకం",
        "rcfilters-empty-filter": "చేతనంగా ఉన్న వడపోతకాలేమీ లేవు. మార్పుచేర్పు లన్నిటినీ చూపించాం.",
        "rcfilters-filterlist-title": "వడపోతలు",
        "rcfilters-filter-logactions-label": "చిట్టాల్లోకి చేరిన కార్యకలాపాలు",
        "rcfilters-filter-logactions-description": "నిర్వాహక పనులు, ఖాతాల సృష్టి, పేజీ తొలగింపులు, ఎక్కింపులు...",
        "rcfilters-hideminor-conflicts-typeofchange": "కొన్ని రకాల మార్పులను \"చిన్న\" మార్పులుగా సూచించ జాలరు. అంచేత ఈ వడపోత కింది మార్పు రకాల వడపోతలతో ఘర్షిస్తోంది: $1",
+       "rcfilters-typeofchange-conflicts-hideminor": "ఈ రకపు వడపోత \"చిన్న మార్పుల\" వడపోతతో ఘర్షణ పడుతుంది. కొన్ని రకాల మార్పులను \"చిన్న\" అని సూచించలేం.",
        "rcfilters-filtergroup-lastrevision": "ఇటీవలి కూర్పులు",
        "rcfilters-filter-lastrevision-label": "ఇటీవలి కూర్పు",
        "rcfilters-filter-lastrevision-description": "పేజీలో ఇటీవల జరిగిన చిట్టచివరి మార్పు.",
        "rcfilters-filter-showlinkedto-label": "ఓ పేజీ నుండి లింకై ఉన్న పేజీల్లో జరిగిన మార్పులను చూపించు",
        "rcfilters-filter-showlinkedto-option-label": "ఎంచుకున్న పేజీకి <strong>లింకైన పేజీలు</strong>",
        "rcfilters-target-page-placeholder": "పేజీ (లేదా వర్గం) పేరు ఇవ్వండి",
+       "rcfilters-allcontents-label": "కంటెంటులన్నీ",
+       "rcfilters-alldiscussions-label": "చర్చలన్నీ",
        "rcnotefrom": "<strong>$3, $4</strong> తరువాత జరిగిన {{PLURAL:$5|మార్పు|మార్పులు}} కింద ఇచ్చాం (<strong>$1</strong> దాకా చూపించాం).",
        "rclistfromreset": "తేదీ ఎంపికను రీసెట్ చెయ్యి",
        "rclistfrom": "$3, $2 తో మొదలుపెట్టి ఆ తరువాత జరిగిన మార్పులను చూపించు",
        "minoreditletter": "చి",
        "newpageletter": "కొ",
        "boteditletter": "బా",
+       "unpatrolledletter": "!",
+       "rc-change-size": "$1",
        "rc-change-size-new": "మార్పు తర్వాత $1 {{PLURAL:$1|బైటు|బైట్లు}}",
        "newsectionsummary": "/* $1 */ కొత్త విభాగం",
        "rc-enhanced-expand": "వివరాలను చూపించు",
        "recentchangeslinked-page": "పేజీ పేరు:",
        "recentchangeslinked-to": "లేదంటే, ఇచ్చిన పేజీకి లింకయివున్న పేజీలలో జరిగిన మార్పులను చూపించు",
        "recentchanges-page-added-to-category": "[[:$1]] ను వర్గానికి చేర్చాం",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] à°µà°°à±\8dà°\97ానిà°\95à°¿ à°\9aà±\87à°°à±\8dà°\9aబడిà°\82ది, [[Special:WhatLinksHere/$1|à°\88 à°ªà±\87à°\9cà±\80 à°\87తర à°ªà±\87à°\9cà±\80à°²à±\8dà°²à±\8b à°\9aà±\87à°°à±\8dà°\9aబడింది]]",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] à°µà°°à±\8dà°\97ానిà°\95à°¿ à°\9aà±\87à°°à±\8dà°\9aారà±\81, [[Special:WhatLinksHere/$1|à°\88 à°ªà±\87à°\9cà±\80 à°\87తర à°ªà±\87à°\9cà±\80à°²à±\8dà°²à±\8b à°\9aà±\87à°°à°¿ à°\89ంది]]",
        "recentchanges-page-removed-from-category": "[[:$1]] వర్గం నుండి తీసివేయబడింది",
-       "recentchanges-page-removed-from-category-bundled": "[[:$1]] à°µà°°à±\8dà°\97à°\82 à°¨à±\81à°\82à°¡à°¿ à°¤à±\80సివà±\87యబడిà°\82ది, [[Special:WhatLinksHere/$1|ఈ పేజీ ఇతర పేజీల్లో చేర్చబడింది]]",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] à°µà°°à±\8dà°\97à°\82 à°¨à±\81à°\82à°¡à°¿ à°¤à±\80సివà±\87సారà±\81, [[Special:WhatLinksHere/$1|ఈ పేజీ ఇతర పేజీల్లో చేర్చబడింది]]",
        "autochange-username": "MediaWiki ఆటోమాటిక్ మార్పు",
        "upload": "దస్త్రపు ఎక్కింపు",
        "uploadbtn": "దస్త్రాన్ని ఎక్కించు",
        "uploaddisabledtext": "ఫైళ్ళ ఎక్కింపులను అచేతనం చేసారు.",
        "php-uploaddisabledtext": "PHPలో ఫైలు ఎక్కింపులు అచేతనమై ఉన్నాయి.\nదయచేసి file_uploads అమరికని చూడండి.",
        "uploadscripted": "ఈ ఫైల్లో HTML కోడు గానీ స్క్రిప్టు కోడు గానీ ఉంది. వెబ్ బ్రౌజరు దాన్ని పొరపాటుగా అనువదించే అవకాశం ఉంది.",
+       "upload-scripted-dtd": "అప్రామాణిక DTD డిక్లరేషన్ను కలిగి ఉన్న SVG ఫైళ్ళను అప్‌లోడు చెయ్యలేరు.",
        "uploadscriptednamespace": "ఈ SVG ఫైలులోని పేరుబరి \"<nowiki>$1</nowiki>\" చెల్లనిది",
        "uploadinvalidxml": "ఎక్కించిన ఫైలులోని XML ను పార్సు చెయ్యలేకపోయాం.",
        "uploadvirus": "ఈ ఫైలులో వైరస్‌ ఉంది! వివరాలు: $1",
        "apisandbox-add-multi": "చేర్చు",
        "apisandbox-results": "ఫలితాలు",
        "apisandbox-request-url-label": "అభ్యర్థన URL:",
+       "apisandbox-request-format-json-label": "JSON",
        "apisandbox-request-time": "అభ్యర్ధన సమయం: {{PLURAL:$1|$1 మి.సె.}}",
        "apisandbox-continue": "కొనసాగించు",
        "apisandbox-continue-clear": "తుడిచివేయి",
        "apisandbox-multivalue-all-values": "$1 (అన్ని విలువలు)",
        "booksources": "పుస్తక మూలాలు",
        "booksources-search-legend": "పుస్తక మూలాల కోసం వెతుకు",
+       "booksources-isbn": "ISBN:",
        "booksources-search": "వెతుకు",
        "booksources-text": "కొత్త, పాత పుస్తకాలు అమ్మే ఇతర సైట్లకు లింకులు కింద ఇచ్చాం. మీరు వెతికే పుస్తకాలకు సంబంధించిన మరింత సమాచారం కూడా అక్కడ దొరకొచ్చు:",
        "booksources-invalid-isbn": "మీరిచ్చిన ISBN సరైనదిగా అనిపించుటలేదు; అసలు మూలాన్నుండి కాపీ చేయడంలో పొరపాట్లున్నాయేమో చూసుకోండి.",
        "listgrouprights-rights": "హక్కులు",
        "listgrouprights-helppage": "Help:గుంపు హక్కులు",
        "listgrouprights-members": "(సభ్యుల జాబితా)",
+       "listgrouprights-right-display": "<span class=\"listgrouprights-granted\">$1 <code>($2)</code></span>",
+       "listgrouprights-right-revoked": "<span class=\"listgrouprights-revoked\">$1 <code>($2)</code></span>",
        "listgrouprights-addgroup": "{{PLURAL:$2|గుంపుని|గుంపులను}} చేర్చగలరు: $1",
        "listgrouprights-removegroup": "{{PLURAL:$2|గుంపుని|గుంపులను}} తొలగించగలరు: $1",
        "listgrouprights-addgroup-all": "అన్ని గుంపులను చేర్చగలరు",
        "listgrants": "గ్రాంట్లు",
        "listgrants-grant": "గ్రాంటు",
        "listgrants-rights": "హక్కులు",
+       "listgrants-grant-display": "$1 <code>($2)</code>",
        "trackingcategories": "పహారా కాయు వర్గాలు",
        "trackingcategories-msg": "పహారా కాయు వర్గము",
        "trackingcategories-name": "సందేశం పేరు",
        "emailuserfooter": "ఈ ఈమెయిలును  $1, {{GENDER:$2|$2}} కు {{SITENAME}} లోని \"{{int:emailuser}}\" ఫంక్షను ద్వారా {{GENDER:$1|పంపించారు}}. {{GENDER:$2|మీరు}} ఈ ఈమెయిలుకు జవాబు పంపిస్తే, {{GENDER:$2|మీ}} మెయిలును నేరుగా {{GENDER:$1|ఒరిజినల్ సెండరుకు}} పంపిస్తాం. దీనితో, {{GENDER:$2|మీ}} ఈమెయిలు అడ్రసు {{GENDER:$1|వారికి}} తెలిసిపోతుంది.",
        "usermessage-summary": "వ్యవస్థ సందేశాన్ని వదిలివేస్తున్నాం.",
        "usermessage-editor": "వ్యవస్థ సందేశకులు",
+       "usermessage-template": "MediaWiki:UserMessage",
        "watchlist": "వీక్షణ జాబితా",
        "mywatchlist": "వీక్షణ జాబితా",
        "watchlistfor2": "$1 కొరకు $2",
        "deleting-backlinks-warning": "<strong>హెచ్చరిక:</strong> మీరు తొలగించబోతున్న పేజీకి [[Special:WhatLinksHere/{{FULLPAGENAME}}|ఇతర పేజీల]] నుండి లింకులు ఉన్నాయి. లేదా ఇతర పేజీల్లో అది ట్రాన్స్‍క్లూడు అవుతోంది.",
        "deleting-subpages-warning": "<strong>హెచ్చరిక:</strong> మీరు తొలగించబోతున్న పేజీకి [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|ఒక ఉపపేజీ ఉంది|$1 ఉపపేజీలున్నాయి|51=50 కి పైగా ఉపపేజీలున్నాయి}}]].",
        "rollback": "దిద్దుబాట్లను రద్దుచేయి",
+       "rollback-confirmation-confirm": "నిర్ధారించండి:",
+       "rollback-confirmation-yes": "రోల్‌బ్యాక్ చెయ్యి",
        "rollback-confirmation-no": "రద్దుచేయి",
        "rollbacklink": "రద్దుచేయి",
        "rollbacklinkcount": "$1 {{PLURAL:$1|మార్పును|మార్పులను}} రద్దుచేయి",
        "protect-fallback": "\"$1\" అనుమతి ఉన్న వాడుకరులను మాత్రమే అనుమతించు",
        "protect-level-autoconfirmed": "స్వయన్నిర్ధారిత వాడుకరులను మాత్రమే అనుమతించు",
        "protect-level-sysop": "నిర్వాహకులను మాత్రమే అనుమతించు",
+       "protect-summary-desc": "[$1=$2] ($3)",
        "protect-summary-cascade": "కాస్కేడింగు",
        "protect-expiring": "$1 (UTC)న కాలం చెల్లుతుంది",
        "protect-expiring-local": "$1న కాలం చెల్లుతుంది",
        "undelete-error-long": "ఫైలు $1 తొలగింపును రద్దు పరచడంలో లోపాలు దొర్లాయి",
        "undelete-show-file-confirm": "$2 నాడు $3 సమయాన ఉన్న \"<nowiki>$1</nowiki>\" ఫైలు యొక్క తొలగించిన కూర్పుని మీరు నిజంగానే చూడాలనుకుంటున్నారా?",
        "undelete-show-file-submit": "అవును",
+       "undelete-revision-row2": "$1 ($2) $3 . . $4 $5 $6 $7 $8",
        "namespace": "పేరుబరి:",
        "invert": "ఎంపికను తిరగవెయ్యి",
        "tooltip-invert": "ఎంచుకున్న పేరుబరి (చెక్ చేసి ఉంటే అనుబంధ పేరుబరి కూడా) లోని పేజీల్లో జరిగిన మార్పులను దాచేందుకు ఈ పెట్టెను చెక్ చెయ్యండి",
        "mycontris": "నా మార్పులు",
        "anoncontribs": "మార్పుచేర్పులు",
        "contribsub2": "{{GENDER:$3|$1}} ($2) కొరకు",
+       "contributions-subtitle": "{{GENDER:$3|$1}} కొరకు",
        "contributions-userdoesnotexist": "వాడుకరి ఖాతా \"$1\" నమోదుకాలేదు.",
+       "negative-namespace-not-supported": "నెగటివ్ విలువలున్న పేరుబరులకు మద్దతు లేదు.",
        "nocontribs": "ఈ విధమైన మార్పులేమీ దొరకలేదు.",
        "uctop": "ప్రస్తుత",
        "month": "ఈ నెల నుండి (అంతకు ముందువి):",
        "ipb-confirm": "నిరోధాన్ని ధృవపరచండి",
        "ipb-sitewide": "సైట్ వ్యాప్తంగా",
        "ipb-partial": "పాక్షికం",
+       "ipb-partial-help": "ప్రత్యేకించిన పేజీలు లేదా పేరుబరులు.",
        "ipb-pages-label": "పేజీలు",
+       "ipb-namespaces-label": "పేరుబరులు",
        "badipaddress": "సరైన ఐ.పి. అడ్రసు కాదు",
        "blockipsuccesssub": "నిరోధం విజయవంతం అయింది",
        "blockipsuccesstext": "[[Special:Contributions/$1|$1]] నిరోధించబడింది.<br />\nనిరోధాల సమీక్ష కొరకు [[Special:BlockList|నిరోధాల జాబితా]] చూడండి.",
index fcf42ea..cd77468 100644 (file)
        "newpages-username": "用戶名稱:",
        "speciallogtitlelabel": "目標 (標題或用戶):",
        "checkbox-select": "選擇: $1",
+       "emailuser": "Email 聯絡此用戶",
        "emailusername": "用戶名稱:",
        "wlshowhidebots": "機械人",
        "blanknamespace": "(主要)",
index a239fa0..4213d5f 100644 (file)
  * @ingroup Maintenance
  */
 
-use MediaWiki\Config\ServiceOptions;
-use MediaWiki\Logger\LoggerFactory;
-use MediaWiki\MediaWikiServices;
-
 require_once __DIR__ . '/Maintenance.php';
 
 /**
@@ -81,25 +77,13 @@ class RebuildLocalisationCache extends Maintenance {
 
                $conf = $wgLocalisationCacheConf;
                $conf['manualRecache'] = false; // Allow fallbacks to create CDB files
-               $conf['forceRecache'] = $force || !empty( $conf['forceRecache'] );
+               if ( $force ) {
+                       $conf['forceRecache'] = true;
+               }
                if ( $this->hasOption( 'outdir' ) ) {
                        $conf['storeDirectory'] = $this->getOption( 'outdir' );
                }
-               // XXX Copy-pasted from ServiceWiring.php. Do we need a factory for this one caller?
-               $lc = new LocalisationCacheBulkLoad(
-                       new ServiceOptions(
-                               LocalisationCache::$constructorOptions,
-                               $conf,
-                               MediaWikiServices::getInstance()->getMainConfig()
-                       ),
-                       new LCStoreDB( [] ),
-                       LoggerFactory::getInstance( 'localisation' ),
-                       [ function () {
-                               MediaWikiServices::getInstance()->getResourceLoader()
-                                       ->getMessageBlobStore()->clear();
-                       } ],
-                       MediaWikiServices::getInstance()->getLanguageNameUtils()
-               );
+               $lc = new LocalisationCacheBulkLoad( $conf );
 
                $allCodes = array_keys( Language::fetchLanguageNames( null, 'mwfile' ) );
                if ( $this->hasOption( 'lang' ) ) {
index 99b548e..7c8df1a 100644 (file)
@@ -221,9 +221,6 @@ $wgAutoloadClasses += [
        # tests/phpunit/unit/includes
        'BadFileLookupTest' => "$testDir/phpunit/unit/includes/BadFileLookupTest.php",
 
-       # tests/phpunit/unit/includes/language
-       'LanguageNameUtilsTestTrait' => "$testDir/phpunit/unit/includes/language/LanguageNameUtilsTestTrait.php",
-
        # tests/phpunit/unit/includes/libs/filebackend/fsfile
        'TempFSFileTestTrait' => "$testDir/phpunit/unit/includes/libs/filebackend/fsfile/TempFSFileTestTrait.php",
 
index 5261b19..33518ef 100644 (file)
@@ -404,7 +404,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
 
                $wgRequest = new FauxRequest();
                MediaWiki\Session\SessionManager::resetCache();
-               Language::clearCaches();
        }
 
        public function run( PHPUnit_Framework_TestResult $result = null ) {
index e6cf8c8..208b955 100644 (file)
@@ -942,11 +942,10 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        'address' => '127.0.8.1',
                        'by' => $this->user->getId(),
                        'reason' => 'no reason given',
-                       'timestamp' => $prev + 3600,
+                       'timestamp' => $prev,
                        'auto' => true,
                        'expiry' => 0
                ] );
-               $this->user->mBlock->setTimestamp( 0 );
                $this->assertEquals( [ [ 'autoblockedtext',
                                "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
                                "\u{202A}Useruser\u{202C}", null, 'infinite', '127.0.8.1',
index 6308b82..282188d 100644 (file)
@@ -160,7 +160,6 @@ class ApiQuerySiteinfoTest extends ApiTestCase {
                        'wgExtraInterlanguageLinkPrefixes' => [ 'self' ],
                        'wgExtraLanguageNames' => [ 'self' => 'Recursion' ],
                ] );
-               $this->resetServices();
 
                MessageCache::singleton()->enable();
 
index 39526fb..42957b6 100644 (file)
@@ -1,9 +1,4 @@
 <?php
-
-use MediaWiki\Config\ServiceOptions;
-use MediaWiki\Languages\LanguageNameUtils;
-use Psr\Log\NullLogger;
-
 /**
  * @group Database
  * @group Cache
@@ -24,51 +19,8 @@ class LocalisationCacheTest extends MediaWikiTestCase {
         */
        protected function getMockLocalisationCache() {
                global $IP;
-
-               $mockLangNameUtils = $this->createMock( LanguageNameUtils::class );
-               $mockLangNameUtils->method( 'isValidBuiltInCode' )->will( $this->returnCallback(
-                       function ( $code ) {
-                               // Copy-paste, but it's only one line
-                               return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
-                       }
-               ) );
-               $mockLangNameUtils->method( 'isSupportedLanguage' )->will( $this->returnCallback(
-                       function ( $code ) {
-                               return in_array( $code, [
-                                       'ar',
-                                       'arz',
-                                       'ba',
-                                       'de',
-                                       'en',
-                                       'ksh',
-                                       'ru',
-                               ] );
-                       }
-               ) );
-               $mockLangNameUtils->method( 'getMessagesFileName' )->will( $this->returnCallback(
-                       function ( $code ) {
-                               global $IP;
-                               $code = str_replace( '-', '_', ucfirst( $code ) );
-                               return "$IP/languages/messages/Messages$code.php";
-                       }
-               ) );
-               $mockLangNameUtils->expects( $this->never() )->method( $this->anythingBut(
-                       'isValidBuiltInCode', 'isSupportedLanguage', 'getMessagesFileName'
-               ) );
-
-               $lc = $this->getMockBuilder( LocalisationCache::class )
-                       ->setConstructorArgs( [
-                               new ServiceOptions( LocalisationCache::$constructorOptions, [
-                                       'forceRecache' => false,
-                                       'manualRecache' => false,
-                                       'ExtensionMessagesFiles' => [],
-                                       'MessagesDirs' => [],
-                               ] ),
-                               new LCStoreDB( [] ),
-                               new NullLogger,
-                               [],
-                               $mockLangNameUtils
-                       ] )
+               $lc = $this->getMockBuilder( \LocalisationCache::class )
+                       ->setConstructorArgs( [ [ 'store' => 'detect' ] ] )
                        ->setMethods( [ 'getMessagesDirs' ] )
                        ->getMock();
                $lc->expects( $this->any() )->method( 'getMessagesDirs' )
@@ -79,7 +31,7 @@ class LocalisationCacheTest extends MediaWikiTestCase {
                return $lc;
        }
 
-       public function testPluralRulesFallback() {
+       public function testPuralRulesFallback() {
                $cache = $this->getMockLocalisationCache();
 
                $this->assertEquals(
diff --git a/tests/phpunit/includes/language/ConverterRuleTest.php b/tests/phpunit/includes/language/ConverterRuleTest.php
new file mode 100644 (file)
index 0000000..1e06142
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @covers ConverterRule
+ */
+class ConverterRuleTest extends MediaWikiTestCase {
+
+       public function setUp() {
+               parent::setUp();
+               $this->setMwGlobals( 'wgUser', new User );
+       }
+
+       public function testParseEmpty() {
+               $converter = new LanguageConverter( new Language(), 'en' );
+               $rule = new ConverterRule( '', $converter );
+               $rule->parse();
+
+               $this->assertSame( false, $rule->getTitle(), 'title' );
+               $this->assertSame( [], $rule->getConvTable(), 'conversion table' );
+               $this->assertSame( 'none', $rule->getRulesAction(), 'rules action' );
+       }
+
+}
index 839272f..4bb9d5a 100644 (file)
@@ -39,13 +39,13 @@ class LogFormatterTest extends MediaWikiLangTestCase {
                global $wgExtensionMessagesFiles;
                self::$oldExtMsgFiles = $wgExtensionMessagesFiles;
                $wgExtensionMessagesFiles['LogTests'] = __DIR__ . '/LogTests.i18n.php';
-               Language::clearCaches();
+               Language::getLocalisationCache()->recache( 'en' );
        }
 
        public static function tearDownAfterClass() {
                global $wgExtensionMessagesFiles;
                $wgExtensionMessagesFiles = self::$oldExtMsgFiles;
-               Language::clearCaches();
+               Language::getLocalisationCache()->recache( 'en' );
 
                parent::tearDownAfterClass();
        }
index 6f618a2..2f6fa39 100644 (file)
@@ -3,24 +3,6 @@
 use Wikimedia\TestingAccessWrapper;
 
 class LanguageTest extends LanguageClassesTestCase {
-       use LanguageNameUtilsTestTrait;
-
-       /** @var array Copy of $wgHooks from before we unset LanguageGetTranslatedLanguageNames */
-       private $origHooks;
-
-       public function setUp() {
-               global $wgHooks;
-
-               parent::setUp();
-
-               // Don't allow installed hooks to run, except if a test restores them via origHooks (needed
-               // for testIsKnownLanguageTag_cldr)
-               $this->origHooks = $wgHooks;
-               $newHooks = $wgHooks;
-               unset( $newHooks['LanguageGetTranslatedLanguageNames'] );
-               $this->setMwGlobals( 'wgHooks', $newHooks );
-       }
-
        /**
         * @covers Language::convertDoubleWidth
         * @covers Language::normalizeForSearch
@@ -528,6 +510,84 @@ class LanguageTest extends LanguageClassesTestCase {
                );
        }
 
+       /**
+        * Test Language::isValidBuiltInCode()
+        * @dataProvider provideLanguageCodes
+        * @covers Language::isValidBuiltInCode
+        */
+       public function testBuiltInCodeValidation( $code, $expected, $message = '' ) {
+               $this->assertEquals( $expected,
+                       (bool)Language::isValidBuiltInCode( $code ),
+                       "validating code $code $message"
+               );
+       }
+
+       public static function provideLanguageCodes() {
+               return [
+                       [ 'fr', true, 'Two letters, minor case' ],
+                       [ 'EN', false, 'Two letters, upper case' ],
+                       [ 'tyv', true, 'Three letters' ],
+                       [ 'be-tarask', true, 'With dash' ],
+                       [ 'be-x-old', true, 'With extension (two dashes)' ],
+                       [ 'be_tarask', false, 'Reject underscores' ],
+               ];
+       }
+
+       /**
+        * Test Language::isKnownLanguageTag()
+        * @dataProvider provideKnownLanguageTags
+        * @covers Language::isKnownLanguageTag
+        */
+       public function testKnownLanguageTag( $code, $message = '' ) {
+               $this->assertTrue(
+                       (bool)Language::isKnownLanguageTag( $code ),
+                       "validating code $code - $message"
+               );
+       }
+
+       public static function provideKnownLanguageTags() {
+               return [
+                       [ 'fr', 'simple code' ],
+                       [ 'bat-smg', 'an MW legacy tag' ],
+                       [ 'sgs', 'an internal standard MW name, for which a legacy tag is used externally' ],
+               ];
+       }
+
+       /**
+        * @covers Language::isKnownLanguageTag
+        */
+       public function testKnownCldrLanguageTag() {
+               if ( !class_exists( 'LanguageNames' ) ) {
+                       $this->markTestSkipped( 'The LanguageNames class is not available. '
+                               . 'The CLDR extension is probably not installed.' );
+               }
+
+               $this->assertTrue(
+                       (bool)Language::isKnownLanguageTag( 'pal' ),
+                       'validating code "pal" an ancient language, which probably will '
+                               . 'not appear in Names.php, but appears in CLDR in English'
+               );
+       }
+
+       /**
+        * Negative tests for Language::isKnownLanguageTag()
+        * @dataProvider provideUnKnownLanguageTags
+        * @covers Language::isKnownLanguageTag
+        */
+       public function testUnknownLanguageTag( $code, $message = '' ) {
+               $this->assertFalse(
+                       (bool)Language::isKnownLanguageTag( $code ),
+                       "checking that code $code is invalid - $message"
+               );
+       }
+
+       public static function provideUnknownLanguageTags() {
+               return [
+                       [ 'mw', 'non-existent two-letter code' ],
+                       [ 'foo"<bar', 'very invalid language code' ],
+               ];
+       }
+
        /**
         * Test too short timestamp
         * @expectedException MWException
@@ -1752,6 +1812,12 @@ class LanguageTest extends LanguageClassesTestCase {
        public function testClearCaches() {
                $languageClass = TestingAccessWrapper::newFromClass( Language::class );
 
+               // Populate $dataCache
+               Language::getLocalisationCache()->getItem( 'zh', 'mainpage' );
+               $oldCacheObj = Language::$dataCache;
+               $this->assertNotCount( 0,
+                       TestingAccessWrapper::newFromObject( Language::$dataCache )->loadedItems );
+
                // Populate $mLangObjCache
                $lang = Language::factory( 'en' );
                $this->assertNotCount( 0, Language::$mLangObjCache );
@@ -1764,11 +1830,36 @@ class LanguageTest extends LanguageClassesTestCase {
                $lang->getGrammarTransformations();
                $this->assertNotNull( $languageClass->grammarTransformations );
 
+               // Populate $languageNameCache
+               Language::fetchLanguageNames();
+               $this->assertNotNull( $languageClass->languageNameCache );
+
                Language::clearCaches();
 
+               $this->assertNotSame( $oldCacheObj, Language::$dataCache );
+               $this->assertCount( 0,
+                       TestingAccessWrapper::newFromObject( Language::$dataCache )->loadedItems );
                $this->assertCount( 0, Language::$mLangObjCache );
                $this->assertCount( 0, $languageClass->fallbackLanguageCache );
                $this->assertNull( $languageClass->grammarTransformations );
+               $this->assertNull( $languageClass->languageNameCache );
+       }
+
+       /**
+        * @dataProvider provideIsSupportedLanguage
+        * @covers Language::isSupportedLanguage
+        */
+       public function testIsSupportedLanguage( $code, $expected, $comment ) {
+               $this->assertEquals( $expected, Language::isSupportedLanguage( $code ), $comment );
+       }
+
+       public static function provideIsSupportedLanguage() {
+               return [
+                       [ 'en', true, 'is supported language' ],
+                       [ 'fi', true, 'is supported language' ],
+                       [ 'bunny', false, 'is not supported language' ],
+                       [ 'FI', false, 'is not supported language, input should be in lower case' ],
+               ];
        }
 
        /**
@@ -1874,82 +1965,4 @@ class LanguageTest extends LanguageClassesTestCase {
                        [ 'èl', 'Ll' , 'Non-ASCII is overridden', [ 'è' => 'L' ] ],
                ];
        }
-
-       // The following methods are for LanguageNameUtilsTestTrait
-
-       private function isSupportedLanguage( $code ) {
-               return Language::isSupportedLanguage( $code );
-       }
-
-       private function isValidCode( $code ) {
-               return Language::isValidCode( $code );
-       }
-
-       private function isValidBuiltInCode( $code ) {
-               return Language::isValidBuiltInCode( $code );
-       }
-
-       private function isKnownLanguageTag( $code ) {
-               return Language::isKnownLanguageTag( $code );
-       }
-
-       /**
-        * Call getLanguageName() and getLanguageNames() using the Language static methods.
-        *
-        * @param array $options To set globals for testing Language
-        * @param string $expected
-        * @param string $code
-        * @param mixed ...$otherArgs Optionally, pass $inLanguage and/or $include.
-        */
-       private function assertGetLanguageNames( array $options, $expected, $code, ...$otherArgs ) {
-               if ( $options ) {
-                       foreach ( $options as $key => $val ) {
-                               $this->setMwGlobals( "wg$key", $val );
-                       }
-                       $this->resetServices();
-               }
-               $this->assertSame( $expected,
-                       Language::fetchLanguageNames( ...$otherArgs )[strtolower( $code )] ?? '' );
-               $this->assertSame( $expected, Language::fetchLanguageName( $code, ...$otherArgs ) );
-       }
-
-       private function getLanguageNames( ...$args ) {
-               return Language::fetchLanguageNames( ...$args );
-       }
-
-       private function getLanguageName( ...$args ) {
-               return Language::fetchLanguageName( ...$args );
-       }
-
-       private static function getFileName( ...$args ) {
-               return Language::getFileName( ...$args );
-       }
-
-       private static function getMessagesFileName( $code ) {
-               return Language::getMessagesFileName( $code );
-       }
-
-       private static function getJsonMessagesFileName( $code ) {
-               return Language::getJsonMessagesFileName( $code );
-       }
-
-       /**
-        * @todo This really belongs in the cldr extension's tests.
-        *
-        * @covers MediaWiki\Languages\LanguageNameUtils::isKnownLanguageTag
-        * @covers Language::isKnownLanguageTag
-        */
-       public function testIsKnownLanguageTag_cldr() {
-               if ( !class_exists( 'LanguageNames' ) ) {
-                       $this->markTestSkipped( 'The LanguageNames class is not available. '
-                               . 'The CLDR extension is probably not installed.' );
-               }
-
-               // We need to restore the extension's hook that we removed.
-               $this->setMwGlobals( 'wgHooks', $this->origHooks );
-
-               // "pal" is an ancient language, which probably will not appear in Names.php, but appears in
-               // CLDR in English
-               $this->assertTrue( Language::isKnownLanguageTag( 'pal' ) );
-       }
 }
diff --git a/tests/phpunit/unit/includes/language/LanguageNameUtilsTest.php b/tests/phpunit/unit/includes/language/LanguageNameUtilsTest.php
deleted file mode 100644 (file)
index 6fbd4a2..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-<?php
-
-use MediaWiki\Config\ServiceOptions;
-use MediaWiki\Languages\LanguageNameUtils;
-
-class LanguageNameUtilsTest extends MediaWikiUnitTestCase {
-       /**
-        * @param array $optionsArray
-        */
-       private static function newObj( array $optionsArray = [] ) : LanguageNameUtils {
-               return new LanguageNameUtils( new ServiceOptions(
-                       LanguageNameUtils::$constructorOptions,
-                       $optionsArray,
-                       [
-                               'ExtraLanguageNames' => [],
-                               'LanguageCode' => 'en',
-                               'UsePigLatinVariant' => false,
-                       ]
-               ) );
-       }
-
-       use LanguageNameUtilsTestTrait;
-
-       private function isSupportedLanguage( $code ) {
-               return $this->newObj()->isSupportedLanguage( $code );
-       }
-
-       private function isValidCode( $code ) {
-               return $this->newObj()->isValidCode( $code );
-       }
-
-       private function isValidBuiltInCode( $code ) {
-               return $this->newObj()->isValidBuiltInCode( $code );
-       }
-
-       private function isKnownLanguageTag( $code ) {
-               return $this->newObj()->isKnownLanguageTag( $code );
-       }
-
-       private function assertGetLanguageNames( array $options, $expected, $code, ...$otherArgs ) {
-               $this->assertSame( $expected, $this->newObj( $options )
-                       ->getLanguageNames( ...$otherArgs )[strtolower( $code )] ?? '' );
-               $this->assertSame( $expected,
-                       $this->newObj( $options )->getLanguageName( $code, ...$otherArgs ) );
-       }
-
-       private function getLanguageNames( ...$args ) {
-               return $this->newObj()->getLanguageNames( ...$args );
-       }
-
-       private function getLanguageName( ...$args ) {
-               return $this->newObj()->getLanguageName( ...$args );
-       }
-
-       private static function getFileName( ...$args ) {
-               return self::newObj()->getFileName( ...$args );
-       }
-
-       private static function getMessagesFileName( $code ) {
-               return self::newObj()->getMessagesFileName( $code );
-       }
-
-       private static function getJsonMessagesFileName( $code ) {
-               return self::newObj()->getJsonMessagesFileName( $code );
-       }
-}
diff --git a/tests/phpunit/unit/includes/language/LanguageNameUtilsTestTrait.php b/tests/phpunit/unit/includes/language/LanguageNameUtilsTestTrait.php
deleted file mode 100644 (file)
index bd777e9..0000000
+++ /dev/null
@@ -1,555 +0,0 @@
-<?php
-
-use MediaWiki\Languages\LanguageNameUtils;
-
-const AUTONYMS = LanguageNameUtils::AUTONYMS;
-const ALL = LanguageNameUtils::ALL;
-const DEFINED = LanguageNameUtils::DEFINED;
-const SUPPORTED = LanguageNameUtils::SUPPORTED;
-
-/**
- * For code shared between LanguageNameUtilsTest and LanguageTest.
- */
-trait LanguageNameUtilsTestTrait {
-       abstract protected function isSupportedLanguage( $code );
-
-       /**
-        * @dataProvider provideIsSupportedLanguage
-        * @covers MediaWiki\Languages\LanguageNameUtils::__construct
-        * @covers MediaWiki\Languages\LanguageNameUtils::isSupportedLanguage
-        * @covers Language::isSupportedLanguage
-        */
-       public function testIsSupportedLanguage( $code, $expected ) {
-               $this->assertSame( $expected, $this->isSupportedLanguage( $code ) );
-       }
-
-       public static function provideIsSupportedLanguage() {
-               return [
-                       'en' => [ 'en', true ],
-                       'fi' => [ 'fi', true ],
-                       'bunny' => [ 'bunny', false ],
-                       'qqq' => [ 'qqq', false ],
-                       'uppercase is not considered supported' => [ 'FI', false ],
-               ];
-       }
-
-       abstract protected function isValidCode( $code );
-
-       /**
-        * We don't test that the result is cached, because that should only be noticeable if the
-        * configuration changes in between calls, and 1) that should never happen in normal operation,
-        * 2) if you do it you deserve whatever you get, and 3) once the static Language method is
-        * dropped and the invalid title regex is moved to something injected instead of a static call,
-        * the cache will be undetectable.
-        *
-        * @todo Should we test changes to $wgLegalTitleChars here? Does anybody actually change that?
-        * Is it possible to change it usefully without breaking everything?
-        *
-        * @dataProvider provideIsValidCode
-        * @covers MediaWiki\Languages\LanguageNameUtils::isValidCode
-        * @covers Language::isValidCode
-        *
-        * @param string $code
-        * @param bool $expected
-        */
-       public function testIsValidCode( $code, $expected ) {
-               $this->assertSame( $expected, $this->isValidCode( $code ) );
-       }
-
-       public static function provideIsValidCode() {
-               $ret = [
-                       'en' => [ 'en', true ],
-                       'en-GB' => [ 'en-GB', true ],
-                       'Funny chars' => [ "%!$()*,-.;=?@^_`~\x80\xA2\xFF+", true ],
-                       'Percent escape not allowed' => [ 'a%aF', false ],
-                       'Percent with only one following char is okay' => [ '%a', true ],
-                       'Percent with non-hex following chars is okay' => [ '%AG', true ],
-                       'Named char reference "a"' => [ 'a&a', false ],
-                       'Named char reference "A"' => [ 'a&A', false ],
-                       'Named char reference "0"' => [ 'a&0', false ],
-                       'Named char reference non-ASCII' => [ "a&\x92", false ],
-                       'Numeric char reference' => [ "a&#0", false ],
-                       'Hex char reference 0' => [ "a&#x0", false ],
-                       'Hex char reference A' => [ "a&#xA", false ],
-                       'Lone ampersand is valid for title but not lang code' => [ '&', false ],
-                       'Ampersand followed by just # is valid for title but not lang code' => [ '&#', false ],
-                       'Ampersand followed by # and non-x/digit is valid for title but not lang code' =>
-                               [ '&#a', false ],
-               ];
-               $disallowedChars = ":/\\\000&<>'\"";
-               foreach ( str_split( $disallowedChars ) as $char ) {
-                       $ret["Disallowed character $char"] = [ "a{$char}a", false ];
-               }
-               return $ret;
-       }
-
-       abstract protected function isValidBuiltInCode( $code );
-
-       /**
-        * @dataProvider provideIsValidBuiltInCode
-        * @covers MediaWiki\Languages\LanguageNameUtils::isValidBuiltInCode
-        * @covers Language::isValidBuiltInCode
-        *
-        * @param string $code
-        * @param bool $expected
-        */
-       public function testIsValidBuiltInCode( $code, $expected ) {
-               $this->assertSame( $expected, $this->isValidBuiltInCode( $code ) );
-       }
-
-       public static function provideIsValidBuiltInCode() {
-               return [
-                       'Two letters, lowercase' => [ 'fr', true ],
-                       'Two letters, uppercase' => [ 'EN', false ],
-                       'Three letters' => [ 'tyv', true ],
-                       'With dash' => [ 'be-tarask', true ],
-                       'With extension (two dashes)' => [ 'be-x-old', true ],
-                       'Reject underscores' => [ 'be_tarask', false ],
-                       'One letter' => [ 'a', false ],
-                       'Only digits' => [ '00', true ],
-                       'Only dashes' => [ '--', true ],
-                       'Unreasonably long' => [ str_repeat( 'x', 100 ), true ],
-                       'qqq' => [ 'qqq', true ],
-               ];
-       }
-
-       abstract protected function isKnownLanguageTag( $code );
-
-       /**
-        * @dataProvider provideIsKnownLanguageTag
-        * @covers MediaWiki\Languages\LanguageNameUtils::isKnownLanguageTag
-        * @covers Language::isKnownLanguageTag
-        *
-        * @param string $code
-        * @param bool $expected
-        */
-       public function testIsKnownLanguageTag( $code, $expected ) {
-               $this->assertSame( $expected, $this->isKnownLanguageTag( $code ) );
-       }
-
-       public static function provideIsKnownLanguageTag() {
-               $invalidBuiltInCodes = array_filter( static::provideIsValidBuiltInCode(),
-                       function ( $arr ) {
-                               // If isValidBuiltInCode() returns false, we want to also, but if it returns true,
-                               // we could still return false from isKnownLanguageTag(), so skip those.
-                               return !$arr[1];
-                       }
-               );
-               return array_merge( $invalidBuiltInCodes, [
-                       'Simple code' => [ 'fr', true ],
-                       'An MW legacy tag' => [ 'bat-smg', true ],
-                       'An internal standard MW name, for which a legacy tag is used externally' =>
-                               [ 'sgs', true ],
-                       'Non-existent two-letter code' => [ 'mw', false ],
-                       'Very invalid language code' => [ 'foo"<bar', false ],
-               ] );
-       }
-
-       abstract protected function assertGetLanguageNames(
-               array $options, $expected, $code, ...$otherArgs
-       );
-
-       abstract protected function getLanguageNames( ...$args );
-
-       abstract protected function getLanguageName( ...$args );
-
-       /**
-        * @dataProvider provideGetLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageName
-        * @covers Language::fetchLanguageNames
-        * @covers Language::fetchLanguageName
-        *
-        * @param string $expected
-        * @param string $code
-        * @param mixed ...$otherArgs Optionally, pass $inLanguage and/or $include.
-        */
-       public function testGetLanguageNames( $expected, $code, ...$otherArgs ) {
-               $this->assertGetLanguageNames( [], $expected, $code, ...$otherArgs );
-       }
-
-       public static function provideGetLanguageNames() {
-               // @todo There are probably lots of interesting tests to add here.
-               return [
-                       'Simple code' => [ 'Deutsch', 'de' ],
-                       'Simple code in a different language (doesn\'t work without hook)' =>
-                               [ 'Deutsch', 'de', 'fr' ],
-                       'Invalid code' => [ '', '&' ],
-                       'Pig Latin not enabled' => [ '', 'en-x-piglatin', AUTONYMS, ALL ],
-                       'qqq doesn\'t have a name' => [ '', 'qqq', AUTONYMS, ALL ],
-                       'An MW legacy tag is recognized' => [ 'žemaitėška', 'bat-smg' ],
-                       // @todo Is the next test's result desired?
-                       'An MW legacy tag is not supported' => [ '', 'bat-smg', AUTONYMS, SUPPORTED ],
-                       'An internal standard name, for which a legacy tag is used externally, is supported' =>
-                               [ 'žemaitėška', 'sgs', AUTONYMS, SUPPORTED ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetLanguageNames_withHook
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageName
-        * @covers Language::fetchLanguageNames
-        * @covers Language::fetchLanguageName
-        *
-        * @param string $expected Expected return value of getLanguageName()
-        * @param string $code
-        * @param mixed ...$otherArgs Optionally, pass $inLanguage and/or $include.
-        */
-       public function testGetLanguageNames_withHook( $expected, $code, ...$otherArgs ) {
-               $this->setTemporaryHook( 'LanguageGetTranslatedLanguageNames',
-                       function ( &$names, $inLanguage ) {
-                               switch ( $inLanguage ) {
-                               case 'de':
-                                       $names = [
-                                               'de' => 'Deutsch',
-                                               'en' => 'Englisch',
-                                               'fr' => 'Französisch',
-                                       ];
-                                       break;
-
-                               case 'en':
-                                       $names = [
-                                               'de' => 'German',
-                                               'en' => 'English',
-                                               'fr' => 'French',
-                                               'sqsqsqsq' => '!!?!',
-                                               'bat-smg' => 'Samogitian',
-                                       ];
-                                       break;
-
-                               case 'fr':
-                                       $names = [
-                                               'de' => 'allemand',
-                                               'en' => 'anglais',
-                                               // Deliberate mistake (no cedilla)
-                                               'fr' => 'francais',
-                                       ];
-                                       break;
-                               }
-                       }
-               );
-
-               // Really we could dispense with assertGetLanguageNames() and just call
-               // testGetLanguageNames() here, but it looks weird to call a test method from another test
-               // method.
-               $this->assertGetLanguageNames( [], $expected, $code, ...$otherArgs );
-       }
-
-       public static function provideGetLanguageNames_withHook() {
-               return [
-                       'Simple code in a different language' => [ 'allemand', 'de', 'fr' ],
-                       'Invalid inLanguage defaults to English' => [ 'German', 'de', '&' ],
-                       'If inLanguage not provided, default to autonym' => [ 'Deutsch', 'de' ],
-                       'Hooks ignored for explicitly-requested autonym' => [ 'français', 'fr', 'fr' ],
-                       'Hooks don\'t make a language supported' => [ '', 'bat-smg', 'en', SUPPORTED ],
-                       'Hooks don\'t make a language defined' => [ '', 'sqsqsqsq', 'en', DEFINED ],
-                       'Hooks do make a language name returned with ALL' => [ '!!?!', 'sqsqsqsq', 'en', ALL ],
-               ];
-       }
-
-       /**
-        * @dataProvider provideGetLanguageNames_ExtraLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageName
-        * @covers Language::fetchLanguageNames
-        * @covers Language::fetchLanguageName
-        *
-        * @param string $expected Expected return value of getLanguageName()
-        * @param string $code
-        * @param mixed ...$otherArgs Optionally, pass $inLanguage and/or $include.
-        */
-       public function testGetLanguageNames_ExtraLanguageNames( $expected, $code, ...$otherArgs ) {
-               $this->setTemporaryHook( 'LanguageGetTranslatedLanguageNames',
-                       function ( &$names ) {
-                               $names['de'] = 'die deutsche Sprache';
-                       }
-               );
-               $this->assertGetLanguageNames(
-                       [ 'ExtraLanguageNames' => [ 'de' => 'deutsche Sprache', 'sqsqsqsq' => '!!?!' ] ],
-                       $expected, $code, ...$otherArgs
-               );
-       }
-
-       public static function provideGetLanguageNames_ExtraLanguageNames() {
-               return [
-                       'Simple extra language name' => [ '!!?!', 'sqsqsqsq' ],
-                       'Extra language is defined' => [ '!!?!', 'sqsqsqsq', AUTONYMS, DEFINED ],
-                       'Extra language is not supported' => [ '', 'sqsqsqsq', AUTONYMS, SUPPORTED ],
-                       'Extra language overrides default' => [ 'deutsche Sprache', 'de' ],
-                       'Extra language overrides hook for explicitly requested autonym' =>
-                               [ 'deutsche Sprache', 'de', 'de' ],
-                       'Hook overrides extra language for non-autonym' =>
-                               [ 'die deutsche Sprache', 'de', 'fr' ],
-               ];
-       }
-
-       /**
-        * Test that getLanguageNames() defaults to DEFINED, and getLanguageName() defaults to ALL.
-        *
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageName
-        * @covers Language::fetchLanguageNames
-        * @covers Language::fetchLanguageName
-        */
-       public function testGetLanguageNames_parameterDefault() {
-               $this->setTemporaryHook( 'LanguageGetTranslatedLanguageNames',
-                       function ( &$names ) {
-                               $names = [ 'sqsqsqsq' => '!!?!' ];
-                       }
-               );
-
-               // We use 'en' here because the hook is not run if we're requesting autonyms, although in
-               // this case (language that isn't defined by MediaWiki itself) that behavior seems wrong.
-               $this->assertArrayNotHasKey( 'sqsqsqsq', $this->getLanguageNames(), 'en' );
-
-               $this->assertSame( '!!?!', $this->getLanguageName( 'sqsqsqsq', 'en' ) );
-       }
-
-       /**
-        * @dataProvider provideGetLanguageNames_sorted
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers Language::fetchLanguageNames
-        *
-        * @param mixed ...$args To pass to method
-        */
-       public function testGetLanguageNames_sorted( ...$args ) {
-               $names = $this->getLanguageNames( ...$args );
-               $sortedNames = $names;
-               ksort( $sortedNames );
-               $this->assertSame( $sortedNames, $names );
-       }
-
-       public static function provideGetLanguageNames_sorted() {
-               return [
-                       [],
-                       [ AUTONYMS ],
-                       [ AUTONYMS, 'mw' ],
-                       [ AUTONYMS, ALL ],
-                       [ AUTONYMS, SUPPORTED ],
-                       [ 'he', 'mw' ],
-                       [ 'he', ALL ],
-                       [ 'he', SUPPORTED ],
-               ];
-       }
-
-       /**
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers Language::fetchLanguageNames
-        */
-       public function testGetLanguageNames_hookNotCalledForAutonyms() {
-               $count = 0;
-               $this->setTemporaryHook( 'LanguageGetTranslatedLanguageNames',
-                       function () use ( &$count ) {
-                               $count++;
-                       }
-               );
-
-               $this->getLanguageNames();
-               $this->assertSame( 0, $count, 'Hook must not be called for autonyms' );
-
-               // We test elsewhere that the hook works, but the following verifies that our test is
-               // working and $count isn't being incremented above only because we're checking autonyms.
-               $this->getLanguageNames( 'fr' );
-               $this->assertSame( 1, $count, 'Hook must be called for non-autonyms' );
-       }
-
-       /**
-        * @dataProvider provideGetLanguageNames_pigLatin
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageName
-        * @covers Language::fetchLanguageNames
-        * @covers Language::fetchLanguageName
-        *
-        * @param string $expected
-        * @param mixed ...$otherArgs Optionally, pass $inLanguage and/or $include.
-        */
-       public function testGetLanguageNames_pigLatin( $expected, ...$otherArgs ) {
-               $this->setTemporaryHook( 'LanguageGetTranslatedLanguageNames',
-                       function ( &$names, $inLanguage ) {
-                               switch ( $inLanguage ) {
-                               case 'fr':
-                                       $names = [ 'en-x-piglatin' => 'latin de cochons' ];
-                                       break;
-
-                               case 'en-x-piglatin':
-                                       // Deliberately lowercase
-                                       $names = [ 'en-x-piglatin' => 'igpay atinlay' ];
-                                       break;
-                               }
-                       }
-               );
-
-               $this->assertGetLanguageNames(
-                       [ 'UsePigLatinVariant' => true ], $expected, 'en-x-piglatin', ...$otherArgs );
-       }
-
-       public static function provideGetLanguageNames_pigLatin() {
-               return [
-                       'Simple test' => [ 'Igpay Atinlay' ],
-                       'Not supported' => [ '', AUTONYMS, SUPPORTED ],
-                       'Foreign language' => [ 'latin de cochons', 'fr' ],
-                       'Hook doesn\'t override explicit autonym' =>
-                               [ 'Igpay Atinlay', 'en-x-piglatin', 'en-x-piglatin' ],
-               ];
-       }
-
-       /**
-        * Just for the sake of completeness, test that ExtraLanguageNames will not override the name
-        * for pig Latin. Nobody actually cares about this and if anything current behavior is probably
-        * wrong, but once we're testing the whole file we may as well be comprehensive.
-        *
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNames
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageNamesUncached
-        * @covers MediaWiki\Languages\LanguageNameUtils::getLanguageName
-        * @covers Language::fetchLanguageNames
-        * @covers Language::fetchLanguageName
-        */
-       public function testGetLanguageNames_pigLatinAndExtraLanguageNames() {
-               $this->assertGetLanguageNames(
-                       [
-                               'UsePigLatinVariant' => true,
-                               'ExtraLanguageNames' => [ 'en-x-piglatin' => 'igpay atinlay' ]
-                       ],
-                       'Igpay Atinlay',
-                       'en-x-piglatin'
-               );
-       }
-
-       abstract protected static function getFileName( ...$args );
-
-       /**
-        * @dataProvider provideGetFileName
-        * @covers MediaWiki\Languages\LanguageNameUtils::getFileName
-        * @covers Language::getFileName
-        *
-        * @param string $expected
-        * @param mixed ...$args To pass to method
-        */
-       public function testGetFileName( $expected, ...$args ) {
-               $this->assertSame( $expected, $this->getFileName( ...$args ) );
-       }
-
-       public static function provideGetFileName() {
-               return [
-                       'Simple case' => [ 'MessagesXx.php', 'Messages', 'xx' ],
-                       'With extension' => [ 'MessagesXx.ext', 'Messages', 'xx', '.ext' ],
-                       'Replacing dashes' => [ '!__?', '!', '--', '?' ],
-                       'Empty prefix and extension' => [ 'Xx', '', 'xx', '' ],
-                       'Uppercase only first letter' => [ 'Messages_a.php', 'Messages', '-a' ],
-               ];
-       }
-
-       abstract protected function getMessagesFileName( $code );
-
-       /**
-        * @dataProvider provideGetMessagesFileName
-        * @covers MediaWiki\Languages\LanguageNameUtils::getMessagesFileName
-        * @covers Language::getMessagesFileName
-        *
-        * @param string $code
-        * @param string $expected
-        */
-       public function testGetMessagesFileName( $code, $expected ) {
-               $this->assertSame( $expected, $this->getMessagesFileName( $code ) );
-       }
-
-       public static function provideGetMessagesFileName() {
-               global $IP;
-               return [
-                       'Simple case' => [ 'en', "$IP/languages/messages/MessagesEn.php" ],
-                       'Replacing dashes' => [ '--', "$IP/languages/messages/Messages__.php" ],
-                       'Uppercase only first letter' => [ '-a', "$IP/languages/messages/Messages_a.php" ],
-               ];
-       }
-
-       /**
-        * @covers MediaWiki\Languages\LanguageNameUtils::getMessagesFileName
-        * @covers Language::getMessagesFileName
-        */
-       public function testGetMessagesFileName_withHook() {
-               $called = 0;
-
-               $this->setTemporaryHook( 'Language::getMessagesFileName',
-                       function ( $code, &$file ) use ( &$called ) {
-                               global $IP;
-
-                               $called++;
-
-                               $this->assertSame( 'ab-cd', $code );
-                               $this->assertSame( "$IP/languages/messages/MessagesAb_cd.php", $file );
-                               $file = 'bye-bye';
-                       }
-               );
-
-               $this->assertSame( 'bye-bye', $this->getMessagesFileName( 'ab-cd' ) );
-               $this->assertSame( 1, $called );
-       }
-
-       abstract protected function getJsonMessagesFileName( $code );
-
-       /**
-        * @covers MediaWiki\Languages\LanguageNameUtils::getJsonMessagesFileName
-        * @covers Language::getJsonMessagesFileName
-        */
-       public function testGetJsonMessagesFileName() {
-               global $IP;
-
-               // Not so much to test here, one test seems to be enough
-               $expected = "$IP/languages/i18n/en--123.json";
-               $this->assertSame( $expected, $this->getJsonMessagesFileName( 'en--123' ) );
-       }
-
-       /**
-        * getFileName, getMessagesFileName, and getJsonMessagesFileName all throw if they get an
-        * invalid code. To save boilerplate, test them all in one method.
-        *
-        * @dataProvider provideExceptionFromInvalidCode
-        * @covers MediaWiki\Languages\LanguageNameUtils::getFileName
-        * @covers MediaWiki\Languages\LanguageNameUtils::getMessagesFileName
-        * @covers MediaWiki\Languages\LanguageNameUtils::getJsonMessagesFileName
-        * @covers Language::getFileName
-        * @covers Language::getMessagesFileName
-        * @covers Language::getJsonMessagesFileName
-        *
-        * @param callable $callback Will throw when passed $code
-        * @param string $code
-        */
-       public function testExceptionFromInvalidCode( $callback, $code ) {
-               $this->setExpectedException( MWException::class, "Invalid language code \"$code\"" );
-
-               $callback( $code );
-       }
-
-       public static function provideExceptionFromInvalidCode() {
-               $ret = [];
-               foreach ( static::provideIsValidBuiltInCode() as $desc => list( $code, $valid ) ) {
-                       if ( $valid ) {
-                               // Won't get an exception from this one
-                               continue;
-                       }
-
-                       // For getFileName, we define an anonymous function because of the extra first param
-                       $ret["getFileName: $desc"] = [
-                               function ( $code ) {
-                                       return static::getFileName( 'Messages', $code );
-                               },
-                               $code
-                       ];
-
-                       $ret["getMessagesFileName: $desc"] =
-                               [ [ static::class, 'getMessagesFileName' ], $code ];
-
-                       $ret["getJsonMessagesFileName: $desc"] =
-                               [ [ static::class, 'getJsonMessagesFileName' ], $code ];
-               }
-               return $ret;
-       }
-}