Split some Language methods to LanguageNameUtils
These are static methods that have to do with processing language names and codes. I didn't include fallback behavior, because that would mean a circular dependency with LocalisationCache. In the new class, I renamed AS_AUTONYMS to AUTONYMS, and added a class constant DEFINED for 'mw' to match the existing SUPPORTED and ALL. I also renamed fetchLanguageName(s) to getLanguageName(s). There is 100% test coverage for the code in the new class. Change-Id: I245ae94bfc1f62b6af75ea57525139adf2539fe6
This commit is contained in:
parent
e4ce770cbd
commit
2e52f48c2e
16 changed files with 1165 additions and 269 deletions
|
|
@ -462,6 +462,11 @@ because of Phabricator reports.
|
||||||
* TempFSFile::factory() has been deprecated. Use TempFSFileFactory instead.
|
* TempFSFile::factory() has been deprecated. Use TempFSFileFactory instead.
|
||||||
* wfIsBadImage() is deprecated. Use the BadFileLookup service instead.
|
* wfIsBadImage() is deprecated. Use the BadFileLookup service instead.
|
||||||
* Language::getLocalisationCache() is deprecated. Use MediaWikiServices.
|
* 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 ===
|
=== Other changes in 1.34 ===
|
||||||
* …
|
* …
|
||||||
|
|
|
||||||
|
|
@ -892,6 +892,7 @@ $wgAutoloadLocalClasses = [
|
||||||
'MediaWiki\\Languages\\Data\\CrhExceptions' => __DIR__ . '/languages/data/CrhExceptions.php',
|
'MediaWiki\\Languages\\Data\\CrhExceptions' => __DIR__ . '/languages/data/CrhExceptions.php',
|
||||||
'MediaWiki\\Languages\\Data\\Names' => __DIR__ . '/languages/data/Names.php',
|
'MediaWiki\\Languages\\Data\\Names' => __DIR__ . '/languages/data/Names.php',
|
||||||
'MediaWiki\\Languages\\Data\\ZhConversion' => __DIR__ . '/languages/data/ZhConversion.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\\ConsoleLogger' => __DIR__ . '/includes/debug/logger/ConsoleLogger.php',
|
||||||
'MediaWiki\\Logger\\ConsoleSpi' => __DIR__ . '/includes/debug/logger/ConsoleSpi.php',
|
'MediaWiki\\Logger\\ConsoleSpi' => __DIR__ . '/includes/debug/logger/ConsoleSpi.php',
|
||||||
'MediaWiki\\Logger\\LegacyLogger' => __DIR__ . '/includes/debug/logger/LegacyLogger.php',
|
'MediaWiki\\Logger\\LegacyLogger' => __DIR__ . '/includes/debug/logger/LegacyLogger.php',
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ use MediaWiki\Block\BlockManager;
|
||||||
use MediaWiki\Block\BlockRestrictionStore;
|
use MediaWiki\Block\BlockRestrictionStore;
|
||||||
use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
|
use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
|
||||||
use MediaWiki\Http\HttpRequestFactory;
|
use MediaWiki\Http\HttpRequestFactory;
|
||||||
|
use MediaWiki\Languages\LanguageNameUtils;
|
||||||
use MediaWiki\Page\MovePageFactory;
|
use MediaWiki\Page\MovePageFactory;
|
||||||
use MediaWiki\Permissions\PermissionManager;
|
use MediaWiki\Permissions\PermissionManager;
|
||||||
use MediaWiki\Preferences\PreferencesFactory;
|
use MediaWiki\Preferences\PreferencesFactory;
|
||||||
|
|
@ -623,6 +624,14 @@ class MediaWikiServices extends ServiceContainer {
|
||||||
return $this->getService( 'InterwikiLookup' );
|
return $this->getService( 'InterwikiLookup' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.34
|
||||||
|
* @return LanguageNameUtils
|
||||||
|
*/
|
||||||
|
public function getLanguageNameUtils() {
|
||||||
|
return $this->getService( 'LanguageNameUtils' );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 1.28
|
* @since 1.28
|
||||||
* @return LinkCache
|
* @return LinkCache
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
|
||||||
use MediaWiki\Http\HttpRequestFactory;
|
use MediaWiki\Http\HttpRequestFactory;
|
||||||
use MediaWiki\Interwiki\ClassicInterwikiLookup;
|
use MediaWiki\Interwiki\ClassicInterwikiLookup;
|
||||||
use MediaWiki\Interwiki\InterwikiLookup;
|
use MediaWiki\Interwiki\InterwikiLookup;
|
||||||
|
use MediaWiki\Languages\LanguageNameUtils;
|
||||||
use MediaWiki\Linker\LinkRenderer;
|
use MediaWiki\Linker\LinkRenderer;
|
||||||
use MediaWiki\Linker\LinkRendererFactory;
|
use MediaWiki\Linker\LinkRendererFactory;
|
||||||
use MediaWiki\Logger\LoggerFactory;
|
use MediaWiki\Logger\LoggerFactory;
|
||||||
|
|
@ -255,6 +256,13 @@ return [
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'LanguageNameUtils' => function ( MediaWikiServices $services ) : LanguageNameUtils {
|
||||||
|
return new LanguageNameUtils( new ServiceOptions(
|
||||||
|
LanguageNameUtils::$constructorOptions,
|
||||||
|
$services->getMainConfig()
|
||||||
|
) );
|
||||||
|
},
|
||||||
|
|
||||||
'LinkCache' => function ( MediaWikiServices $services ) : LinkCache {
|
'LinkCache' => function ( MediaWikiServices $services ) : LinkCache {
|
||||||
return new LinkCache(
|
return new LinkCache(
|
||||||
$services->getTitleFormatter(),
|
$services->getTitleFormatter(),
|
||||||
|
|
@ -326,7 +334,8 @@ return [
|
||||||
$logger,
|
$logger,
|
||||||
[ function () use ( $services ) {
|
[ function () use ( $services ) {
|
||||||
$services->getResourceLoader()->getMessageBlobStore()->clear();
|
$services->getResourceLoader()->getMessageBlobStore()->clear();
|
||||||
} ]
|
} ],
|
||||||
|
$services->getLanguageNameUtils()
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
use CLDRPluralRuleParser\Evaluator;
|
use CLDRPluralRuleParser\Evaluator;
|
||||||
use CLDRPluralRuleParser\Error as CLDRPluralRuleError;
|
use CLDRPluralRuleParser\Error as CLDRPluralRuleError;
|
||||||
use MediaWiki\Config\ServiceOptions;
|
use MediaWiki\Config\ServiceOptions;
|
||||||
|
use MediaWiki\Languages\LanguageNameUtils;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -73,6 +74,9 @@ class LocalisationCache {
|
||||||
/** @var callable[] See comment for parameter in constructor */
|
/** @var callable[] See comment for parameter in constructor */
|
||||||
private $clearStoreCallbacks;
|
private $clearStoreCallbacks;
|
||||||
|
|
||||||
|
/** @var LanguageNameUtils */
|
||||||
|
private $langNameUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A 2-d associative array, code/key, where presence indicates that the item
|
* A 2-d associative array, code/key, where presence indicates that the item
|
||||||
* is loaded. Value arbitrary.
|
* is loaded. Value arbitrary.
|
||||||
|
|
@ -212,13 +216,15 @@ class LocalisationCache {
|
||||||
* @param callable[] $clearStoreCallbacks To be called whenever the cache is cleared. Can be
|
* @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
|
* used to clear other caches that depend on this one, such as ResourceLoader's
|
||||||
* MessageBlobStore.
|
* MessageBlobStore.
|
||||||
|
* @param LanguageNameUtils $langNameUtils
|
||||||
* @throws MWException
|
* @throws MWException
|
||||||
*/
|
*/
|
||||||
function __construct(
|
function __construct(
|
||||||
ServiceOptions $options,
|
ServiceOptions $options,
|
||||||
LCStore $store,
|
LCStore $store,
|
||||||
LoggerInterface $logger,
|
LoggerInterface $logger,
|
||||||
array $clearStoreCallbacks = []
|
array $clearStoreCallbacks,
|
||||||
|
LanguageNameUtils $langNameUtils
|
||||||
) {
|
) {
|
||||||
$options->assertRequiredOptions( self::$constructorOptions );
|
$options->assertRequiredOptions( self::$constructorOptions );
|
||||||
|
|
||||||
|
|
@ -226,6 +232,7 @@ class LocalisationCache {
|
||||||
$this->store = $store;
|
$this->store = $store;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->clearStoreCallbacks = $clearStoreCallbacks;
|
$this->clearStoreCallbacks = $clearStoreCallbacks;
|
||||||
|
$this->langNameUtils = $langNameUtils;
|
||||||
|
|
||||||
// Keep this separate from $this->options so it can be mutable
|
// Keep this separate from $this->options so it can be mutable
|
||||||
$this->manualRecache = $options->get( 'manualRecache' );
|
$this->manualRecache = $options->get( 'manualRecache' );
|
||||||
|
|
@ -438,7 +445,7 @@ class LocalisationCache {
|
||||||
$this->initialisedLangs[$code] = true;
|
$this->initialisedLangs[$code] = true;
|
||||||
|
|
||||||
# If the code is of the wrong form for a Messages*.php file, do a shallow fallback
|
# If the code is of the wrong form for a Messages*.php file, do a shallow fallback
|
||||||
if ( !Language::isValidBuiltInCode( $code ) ) {
|
if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
|
||||||
$this->initShallowFallback( $code, 'en' );
|
$this->initShallowFallback( $code, 'en' );
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -446,7 +453,7 @@ class LocalisationCache {
|
||||||
|
|
||||||
# Recache the data if necessary
|
# Recache the data if necessary
|
||||||
if ( !$this->manualRecache && $this->isExpired( $code ) ) {
|
if ( !$this->manualRecache && $this->isExpired( $code ) ) {
|
||||||
if ( Language::isSupportedLanguage( $code ) ) {
|
if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
|
||||||
$this->recache( $code );
|
$this->recache( $code );
|
||||||
} elseif ( $code === 'en' ) {
|
} elseif ( $code === 'en' ) {
|
||||||
throw new MWException( 'MessagesEn.php is missing.' );
|
throw new MWException( 'MessagesEn.php is missing.' );
|
||||||
|
|
@ -684,7 +691,7 @@ class LocalisationCache {
|
||||||
global $IP;
|
global $IP;
|
||||||
|
|
||||||
// This reads in the PHP i18n file with non-messages l10n data
|
// This reads in the PHP i18n file with non-messages l10n data
|
||||||
$fileName = Language::getMessagesFileName( $code );
|
$fileName = $this->langNameUtils->getMessagesFileName( $code );
|
||||||
if ( !file_exists( $fileName ) ) {
|
if ( !file_exists( $fileName ) ) {
|
||||||
$data = [];
|
$data = [];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Methods for dealing with language codes.
|
* Methods for dealing with language codes.
|
||||||
* @todo Move some of the code-related static methods out of Language into this class
|
|
||||||
*
|
*
|
||||||
* @since 1.29
|
* @since 1.29
|
||||||
* @ingroup Language
|
* @ingroup Language
|
||||||
|
|
|
||||||
319
includes/language/LanguageNameUtils.php
Normal file
319
includes/language/LanguageNameUtils.php
Normal file
|
|
@ -0,0 +1,319 @@
|
||||||
|
<?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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,8 +27,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use CLDRPluralRuleParser\Evaluator;
|
use CLDRPluralRuleParser\Evaluator;
|
||||||
|
use MediaWiki\Languages\LanguageNameUtils;
|
||||||
use MediaWiki\MediaWikiServices;
|
use MediaWiki\MediaWikiServices;
|
||||||
use Wikimedia\Assert\Assert;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internationalisation code
|
* Internationalisation code
|
||||||
|
|
@ -38,21 +38,24 @@ class Language {
|
||||||
/**
|
/**
|
||||||
* Return autonyms in fetchLanguageName(s).
|
* Return autonyms in fetchLanguageName(s).
|
||||||
* @since 1.32
|
* @since 1.32
|
||||||
|
* @deprecated since 1.34, LanguageNameUtils::AUTONYMS
|
||||||
*/
|
*/
|
||||||
const AS_AUTONYMS = null;
|
const AS_AUTONYMS = LanguageNameUtils::AUTONYMS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all known languages in fetchLanguageName(s).
|
* Return all known languages in fetchLanguageName(s).
|
||||||
* @since 1.32
|
* @since 1.32
|
||||||
|
* @deprecated since 1.34, use LanguageNameUtils::ALL
|
||||||
*/
|
*/
|
||||||
const ALL = 'all';
|
const ALL = LanguageNameUtils::ALL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return in fetchLanguageName(s) only the languages for which we have at
|
* Return in fetchLanguageName(s) only the languages for which we have at
|
||||||
* least some localisation.
|
* least some localisation.
|
||||||
* @since 1.32
|
* @since 1.32
|
||||||
|
* @deprecated since 1.34, use LanguageNameUtils::SUPPORTED
|
||||||
*/
|
*/
|
||||||
const SUPPORTED = 'mwfile';
|
const SUPPORTED = LanguageNameUtils::SUPPORTED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var LanguageConverter
|
* @var LanguageConverter
|
||||||
|
|
@ -78,6 +81,9 @@ class Language {
|
||||||
/** @var LocalisationCache */
|
/** @var LocalisationCache */
|
||||||
private $localisationCache;
|
private $localisationCache;
|
||||||
|
|
||||||
|
/** @var LanguageNameUtils */
|
||||||
|
private $langNameUtils;
|
||||||
|
|
||||||
public static $mLangObjCache = [];
|
public static $mLangObjCache = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -92,6 +98,7 @@ class Language {
|
||||||
*/
|
*/
|
||||||
const STRICT_FALLBACKS = 1;
|
const STRICT_FALLBACKS = 1;
|
||||||
|
|
||||||
|
// TODO Make these const once we drop HHVM support (T192166)
|
||||||
public static $mWeekdayMsgs = [
|
public static $mWeekdayMsgs = [
|
||||||
'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
|
'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
|
||||||
'friday', 'saturday'
|
'friday', 'saturday'
|
||||||
|
|
@ -176,12 +183,6 @@ class Language {
|
||||||
*/
|
*/
|
||||||
private static $grammarTransformations;
|
private static $grammarTransformations;
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache for language names
|
|
||||||
* @var HashBagOStuff|null
|
|
||||||
*/
|
|
||||||
private static $languageNameCache;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unicode directional formatting characters, for embedBidi()
|
* Unicode directional formatting characters, for embedBidi()
|
||||||
*/
|
*/
|
||||||
|
|
@ -237,11 +238,12 @@ class Language {
|
||||||
* @return Language
|
* @return Language
|
||||||
*/
|
*/
|
||||||
protected static function newFromCode( $code, $fallback = false ) {
|
protected static function newFromCode( $code, $fallback = false ) {
|
||||||
if ( !self::isValidCode( $code ) ) {
|
$langNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils();
|
||||||
|
if ( !$langNameUtils->isValidCode( $code ) ) {
|
||||||
throw new MWException( "Invalid language code \"$code\"" );
|
throw new MWException( "Invalid language code \"$code\"" );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !self::isValidBuiltInCode( $code ) ) {
|
if ( !$langNameUtils->isValidBuiltInCode( $code ) ) {
|
||||||
// It's not possible to customise this code with class files, so
|
// It's not possible to customise this code with class files, so
|
||||||
// just return a Language object. This is to support uselang= hacks.
|
// just return a Language object. This is to support uselang= hacks.
|
||||||
$lang = new Language;
|
$lang = new Language;
|
||||||
|
|
@ -260,7 +262,7 @@ class Language {
|
||||||
// Keep trying the fallback list until we find an existing class
|
// Keep trying the fallback list until we find an existing class
|
||||||
$fallbacks = self::getFallbacksFor( $code );
|
$fallbacks = self::getFallbacksFor( $code );
|
||||||
foreach ( $fallbacks as $fallbackCode ) {
|
foreach ( $fallbacks as $fallbackCode ) {
|
||||||
if ( !self::isValidBuiltInCode( $fallbackCode ) ) {
|
if ( !$langNameUtils->isValidBuiltInCode( $fallbackCode ) ) {
|
||||||
throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
|
throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,32 +288,25 @@ class Language {
|
||||||
}
|
}
|
||||||
if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
|
if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
|
||||||
MediaWikiServices::getInstance()->resetServiceForTesting( 'LocalisationCache' );
|
MediaWikiServices::getInstance()->resetServiceForTesting( 'LocalisationCache' );
|
||||||
|
MediaWikiServices::getInstance()->resetServiceForTesting( 'LanguageNameUtils' );
|
||||||
}
|
}
|
||||||
self::$mLangObjCache = [];
|
self::$mLangObjCache = [];
|
||||||
self::$fallbackLanguageCache = [];
|
self::$fallbackLanguageCache = [];
|
||||||
self::$grammarTransformations = null;
|
self::$grammarTransformations = null;
|
||||||
self::$languageNameCache = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether any localisation is available for that language tag
|
* Checks whether any localisation is available for that language tag
|
||||||
* in MediaWiki (MessagesXx.php exists).
|
* in MediaWiki (MessagesXx.php exists).
|
||||||
*
|
*
|
||||||
|
* @deprecated since 1.34, use LanguageNameUtils
|
||||||
* @param string $code Language tag (in lower case)
|
* @param string $code Language tag (in lower case)
|
||||||
* @return bool Whether language is supported
|
* @return bool Whether language is supported
|
||||||
* @since 1.21
|
* @since 1.21
|
||||||
*/
|
*/
|
||||||
public static function isSupportedLanguage( $code ) {
|
public static function isSupportedLanguage( $code ) {
|
||||||
if ( !self::isValidBuiltInCode( $code ) ) {
|
return MediaWikiServices::getInstance()->getLanguageNameUtils()
|
||||||
return false;
|
->isSupportedLanguage( $code );
|
||||||
}
|
|
||||||
|
|
||||||
if ( $code === 'qqq' ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return is_readable( self::getMessagesFileName( $code ) ) ||
|
|
||||||
is_readable( self::getJsonMessagesFileName( $code ) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -379,63 +374,45 @@ class Language {
|
||||||
* not it exists. This includes codes which are used solely for
|
* not it exists. This includes codes which are used solely for
|
||||||
* customisation via the MediaWiki namespace.
|
* customisation via the MediaWiki namespace.
|
||||||
*
|
*
|
||||||
|
* @deprecated since 1.34, use LanguageNameUtils
|
||||||
|
*
|
||||||
* @param string $code
|
* @param string $code
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function isValidCode( $code ) {
|
public static function isValidCode( $code ) {
|
||||||
static $cache = [];
|
return MediaWikiServices::getInstance()->getLanguageNameUtils()->isValidCode( $code );
|
||||||
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
|
* Returns true if a language code is of a valid form for the purposes of
|
||||||
* internal customisation of MediaWiki, via Messages*.php or *.json.
|
* internal customisation of MediaWiki, via Messages*.php or *.json.
|
||||||
*
|
*
|
||||||
|
* @deprecated since 1.34, use LanguageNameUtils
|
||||||
|
*
|
||||||
* @param string $code
|
* @param string $code
|
||||||
*
|
*
|
||||||
* @since 1.18
|
* @since 1.18
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function isValidBuiltInCode( $code ) {
|
public static function isValidBuiltInCode( $code ) {
|
||||||
Assert::parameterType( 'string', $code, '$code' );
|
return MediaWikiServices::getInstance()->getLanguageNameUtils()
|
||||||
|
->isValidBuiltInCode( $code );
|
||||||
return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if a language code is an IETF tag known to MediaWiki.
|
* Returns true if a language code is an IETF tag known to MediaWiki.
|
||||||
*
|
*
|
||||||
|
* @deprecated since 1.34, use LanguageNameUtils
|
||||||
|
*
|
||||||
* @param string $tag
|
* @param string $tag
|
||||||
*
|
*
|
||||||
* @since 1.21
|
* @since 1.21
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function isKnownLanguageTag( $tag ) {
|
public static function isKnownLanguageTag( $tag ) {
|
||||||
// Quick escape for invalid input to avoid exceptions down the line
|
return MediaWikiServices::getInstance()->getLanguageNameUtils()
|
||||||
// when code tries to process tags which are not valid at all.
|
->isKnownLanguageTag( $tag );
|
||||||
if ( !self::isValidBuiltInCode( $tag ) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( isset( MediaWiki\Languages\Data\Names::$names[$tag] )
|
|
||||||
|| self::fetchLanguageName( $tag, $tag ) !== ''
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -456,7 +433,9 @@ class Language {
|
||||||
} else {
|
} else {
|
||||||
$this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
|
$this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
|
||||||
}
|
}
|
||||||
$this->localisationCache = MediaWikiServices::getInstance()->getLocalisationCache();
|
$services = MediaWikiServices::getInstance();
|
||||||
|
$this->localisationCache = $services->getLocalisationCache();
|
||||||
|
$this->langNameUtils = $services->getLanguageNameUtils();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -761,7 +740,7 @@ class Language {
|
||||||
if ( $usemsg && wfMessage( $msg )->exists() ) {
|
if ( $usemsg && wfMessage( $msg )->exists() ) {
|
||||||
return $this->getMessageFromDB( $msg );
|
return $this->getMessageFromDB( $msg );
|
||||||
}
|
}
|
||||||
$name = self::fetchLanguageName( $code );
|
$name = $this->langNameUtils->getLanguageName( $code );
|
||||||
if ( $name ) {
|
if ( $name ) {
|
||||||
return $name; # if it's defined as a language name, show that
|
return $name; # if it's defined as a language name, show that
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -822,6 +801,8 @@ class Language {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an array of language names, indexed by code.
|
* 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
|
* @param null|string $inLanguage Code of language in which to return the names
|
||||||
* Use self::AS_AUTONYMS for autonyms (native names)
|
* Use self::AS_AUTONYMS for autonyms (native names)
|
||||||
* @param string $include One of:
|
* @param string $include One of:
|
||||||
|
|
@ -832,95 +813,12 @@ class Language {
|
||||||
* @since 1.20
|
* @since 1.20
|
||||||
*/
|
*/
|
||||||
public static function fetchLanguageNames( $inLanguage = self::AS_AUTONYMS, $include = 'mw' ) {
|
public static function fetchLanguageNames( $inLanguage = self::AS_AUTONYMS, $include = 'mw' ) {
|
||||||
$cacheKey = $inLanguage === self::AS_AUTONYMS ? 'null' : $inLanguage;
|
return MediaWikiServices::getInstance()->getLanguageNameUtils()
|
||||||
$cacheKey .= ":$include";
|
->getLanguageNames( $inLanguage, $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 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
|
* @param null|string $inLanguage Code of language in which to return the name
|
||||||
* (SELF::AS_AUTONYMS for autonyms)
|
* (SELF::AS_AUTONYMS for autonyms)
|
||||||
|
|
@ -933,9 +831,8 @@ class Language {
|
||||||
$inLanguage = self::AS_AUTONYMS,
|
$inLanguage = self::AS_AUTONYMS,
|
||||||
$include = self::ALL
|
$include = self::ALL
|
||||||
) {
|
) {
|
||||||
$code = strtolower( $code );
|
return MediaWikiServices::getInstance()->getLanguageNameUtils()
|
||||||
$array = self::fetchLanguageNames( $inLanguage, $include );
|
->getLanguageName( $code, $inLanguage, $include );
|
||||||
return !array_key_exists( $code, $array ) ? '' : $array[$code];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -4441,6 +4338,8 @@ class Language {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of a file for a certain language code
|
* 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 $prefix Prepend this to the filename
|
||||||
* @param string $code Language code
|
* @param string $code Language code
|
||||||
* @param string $suffix Append this to the filename
|
* @param string $suffix Append this to the filename
|
||||||
|
|
@ -4448,38 +4347,30 @@ class Language {
|
||||||
* @return string $prefix . $mangledCode . $suffix
|
* @return string $prefix . $mangledCode . $suffix
|
||||||
*/
|
*/
|
||||||
public static function getFileName( $prefix, $code, $suffix = '.php' ) {
|
public static function getFileName( $prefix, $code, $suffix = '.php' ) {
|
||||||
if ( !self::isValidBuiltInCode( $code ) ) {
|
return MediaWikiServices::getInstance()->getLanguageNameUtils()
|
||||||
throw new MWException( "Invalid language code \"$code\"" );
|
->getFileName( $prefix, $code, $suffix );
|
||||||
}
|
|
||||||
|
|
||||||
return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated since 1.34, use LanguageNameUtils
|
||||||
* @param string $code
|
* @param string $code
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function getMessagesFileName( $code ) {
|
public static function getMessagesFileName( $code ) {
|
||||||
global $IP;
|
return MediaWikiServices::getInstance()->getLanguageNameUtils()
|
||||||
$file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
|
->getMessagesFileName( $code );
|
||||||
Hooks::run( 'Language::getMessagesFileName', [ $code, &$file ] );
|
|
||||||
return $file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated since 1.34, use LanguageNameUtils
|
||||||
* @param string $code
|
* @param string $code
|
||||||
* @return string
|
* @return string
|
||||||
* @throws MWException
|
* @throws MWException
|
||||||
* @since 1.23
|
* @since 1.23
|
||||||
*/
|
*/
|
||||||
public static function getJsonMessagesFileName( $code ) {
|
public static function getJsonMessagesFileName( $code ) {
|
||||||
global $IP;
|
return MediaWikiServices::getInstance()->getLanguageNameUtils()
|
||||||
|
->getJsonMessagesFileName( $code );
|
||||||
if ( !self::isValidBuiltInCode( $code ) ) {
|
|
||||||
throw new MWException( "Invalid language code \"$code\"" );
|
|
||||||
}
|
|
||||||
|
|
||||||
return "$IP/languages/i18n/$code.json";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ namespace MediaWiki\Languages\Data;
|
||||||
* If you are adding support for such a language, add it also to
|
* If you are adding support for such a language, add it also to
|
||||||
* the relevant section in shared.css.
|
* the relevant section in shared.css.
|
||||||
*
|
*
|
||||||
* Do not use this class directly. Use Language::fetchLanguageNames(), which
|
* Do not use this class directly. Use LanguageNameUtils::getLanguageNames(), which
|
||||||
* includes support for the CLDR extension.
|
* includes support for the CLDR extension.
|
||||||
*
|
*
|
||||||
* @ingroup Language
|
* @ingroup Language
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,8 @@ class RebuildLocalisationCache extends Maintenance {
|
||||||
[ function () {
|
[ function () {
|
||||||
MediaWikiServices::getInstance()->getResourceLoader()
|
MediaWikiServices::getInstance()->getResourceLoader()
|
||||||
->getMessageBlobStore()->clear();
|
->getMessageBlobStore()->clear();
|
||||||
} ]
|
} ],
|
||||||
|
MediaWikiServices::getInstance()->getLanguageNameUtils()
|
||||||
);
|
);
|
||||||
|
|
||||||
$allCodes = array_keys( Language::fetchLanguageNames( null, 'mwfile' ) );
|
$allCodes = array_keys( Language::fetchLanguageNames( null, 'mwfile' ) );
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,9 @@ $wgAutoloadClasses += [
|
||||||
# tests/phpunit/unit/includes
|
# tests/phpunit/unit/includes
|
||||||
'BadFileLookupTest' => "$testDir/phpunit/unit/includes/BadFileLookupTest.php",
|
'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
|
# tests/phpunit/unit/includes/libs/filebackend/fsfile
|
||||||
'TempFSFileTestTrait' => "$testDir/phpunit/unit/includes/libs/filebackend/fsfile/TempFSFileTestTrait.php",
|
'TempFSFileTestTrait' => "$testDir/phpunit/unit/includes/libs/filebackend/fsfile/TempFSFileTestTrait.php",
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,7 @@ class ApiQuerySiteinfoTest extends ApiTestCase {
|
||||||
'wgExtraInterlanguageLinkPrefixes' => [ 'self' ],
|
'wgExtraInterlanguageLinkPrefixes' => [ 'self' ],
|
||||||
'wgExtraLanguageNames' => [ 'self' => 'Recursion' ],
|
'wgExtraLanguageNames' => [ 'self' => 'Recursion' ],
|
||||||
] );
|
] );
|
||||||
|
$this->resetServices();
|
||||||
|
|
||||||
MessageCache::singleton()->enable();
|
MessageCache::singleton()->enable();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use MediaWiki\Config\ServiceOptions;
|
use MediaWiki\Config\ServiceOptions;
|
||||||
|
use MediaWiki\Languages\LanguageNameUtils;
|
||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -24,6 +25,37 @@ class LocalisationCacheTest extends MediaWikiTestCase {
|
||||||
protected function getMockLocalisationCache() {
|
protected function getMockLocalisationCache() {
|
||||||
global $IP;
|
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 )
|
$lc = $this->getMockBuilder( LocalisationCache::class )
|
||||||
->setConstructorArgs( [
|
->setConstructorArgs( [
|
||||||
new ServiceOptions( LocalisationCache::$constructorOptions, [
|
new ServiceOptions( LocalisationCache::$constructorOptions, [
|
||||||
|
|
@ -33,7 +65,9 @@ class LocalisationCacheTest extends MediaWikiTestCase {
|
||||||
'MessagesDirs' => [],
|
'MessagesDirs' => [],
|
||||||
] ),
|
] ),
|
||||||
new LCStoreDB( [] ),
|
new LCStoreDB( [] ),
|
||||||
new NullLogger
|
new NullLogger,
|
||||||
|
[],
|
||||||
|
$mockLangNameUtils
|
||||||
] )
|
] )
|
||||||
->setMethods( [ 'getMessagesDirs' ] )
|
->setMethods( [ 'getMessagesDirs' ] )
|
||||||
->getMock();
|
->getMock();
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,24 @@
|
||||||
use Wikimedia\TestingAccessWrapper;
|
use Wikimedia\TestingAccessWrapper;
|
||||||
|
|
||||||
class LanguageTest extends LanguageClassesTestCase {
|
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::convertDoubleWidth
|
||||||
* @covers Language::normalizeForSearch
|
* @covers Language::normalizeForSearch
|
||||||
|
|
@ -510,84 +528,6 @@ 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
|
* Test too short timestamp
|
||||||
* @expectedException MWException
|
* @expectedException MWException
|
||||||
|
|
@ -1824,33 +1764,11 @@ class LanguageTest extends LanguageClassesTestCase {
|
||||||
$lang->getGrammarTransformations();
|
$lang->getGrammarTransformations();
|
||||||
$this->assertNotNull( $languageClass->grammarTransformations );
|
$this->assertNotNull( $languageClass->grammarTransformations );
|
||||||
|
|
||||||
// Populate $languageNameCache
|
|
||||||
Language::fetchLanguageNames();
|
|
||||||
$this->assertNotNull( $languageClass->languageNameCache );
|
|
||||||
|
|
||||||
Language::clearCaches();
|
Language::clearCaches();
|
||||||
|
|
||||||
$this->assertCount( 0, Language::$mLangObjCache );
|
$this->assertCount( 0, Language::$mLangObjCache );
|
||||||
$this->assertCount( 0, $languageClass->fallbackLanguageCache );
|
$this->assertCount( 0, $languageClass->fallbackLanguageCache );
|
||||||
$this->assertNull( $languageClass->grammarTransformations );
|
$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' ],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1956,4 +1874,82 @@ class LanguageTest extends LanguageClassesTestCase {
|
||||||
[ 'èl', 'Ll' , 'Non-ASCII is overridden', [ 'è' => 'L' ] ],
|
[ 'è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' ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,555 @@
|
||||||
|
<?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�", false ],
|
||||||
|
'Hex char reference 0' => [ "a�", false ],
|
||||||
|
'Hex char reference A' => [ "a
", 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue