wiki.techinc.nl/includes/language/LanguageFallback.php
Aryeh Gregor 8c4f59db64 New LanguageFallback service
This replaces the static Language methods getFallbackFor(),
getFallbacksFor(), and getFallbacksIncludingSiteLanguage(). There is
100% unit and integration test coverage for the new class.

One deliberate functional change: I changed one place where we threw
MWException to InvalidArgumentException.

Bug: T201405
Depends-On: Ie7a89f6ed7d52a0bc01672019ff92e7ee105a1f3
Change-Id: I49222eb55f1feec5b1dcd40f364cffe0c8801855
2019-10-08 15:11:39 -07:00

141 lines
4.1 KiB
PHP

<?php
namespace MediaWiki\Languages;
use InvalidArgumentException;
use LocalisationCache;
use Wikimedia\Assert\Assert;
/**
* @since 1.35
* @ingroup Language
*/
class LanguageFallback {
/**
* Return a fallback chain for messages in getAll
* @since 1.35
*/
const MESSAGES = 0;
/**
* Return a strict fallback chain in getAll
* @since 1.35
*/
const STRICT = 1;
/** @var string */
private $siteLangCode;
/** @var LocalisationCache */
private $localisationCache;
/** @var LanguageNameUtils */
private $langNameUtils;
/** @var array */
private $fallbackCache = [];
/**
* Do not call this directly. Use MediaWikiServices.
*
* @since 1.35
* @param string $siteLangCode Language code of the site, typically $wgLanguageCode
* @param LocalisationCache $localisationCache
* @param LanguageNameUtils $langNameUtils
*/
public function __construct(
$siteLangCode,
LocalisationCache $localisationCache,
LanguageNameUtils $langNameUtils
) {
$this->siteLangCode = $siteLangCode;
$this->localisationCache = $localisationCache;
$this->langNameUtils = $langNameUtils;
}
/**
* Get the first fallback for a given language.
*
* @since 1.35
* @param string $code
* @return string|null
*/
public function getFirst( $code ) {
return $this->getAll( $code )[0] ?? null;
}
/**
* Get the ordered list of fallback languages.
*
* @since 1.35
* @param string $code Language code
* @param int $mode Fallback mode, either MESSAGES (which always falls back to 'en'), or STRICT
* (which only falls back to 'en' when explicitly defined)
* @throws InvalidArgumentException If $mode is invalid
* @return array List of language codes
*/
public function getAll( $code, $mode = self::MESSAGES ) {
// XXX The LanguageNameUtils dependency is just because of this line, is it needed?
// Especially because isValidBuiltInCode() is just a one-line regex anyway, maybe it should
// actually be static?
if ( $code === 'en' || !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
return [];
}
switch ( $mode ) {
case self::MESSAGES:
// For unknown languages, fallbackSequence returns an empty array. Hardcode fallback
// to 'en' in that case, as English messages are always defined.
$ret = $this->localisationCache->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
break;
case self::STRICT:
// Use this mode when you don't want to fall back to English unless explicitly
// defined, for example when you have language-variant icons and an international
// language-independent fallback.
$ret = $this->localisationCache->getItem( $code, 'originalFallbackSequence' );
break;
default:
throw new InvalidArgumentException( "Invalid fallback mode \"$mode\"" );
}
foreach ( $ret as $fallbackCode ) {
Assert::postcondition( $this->langNameUtils->isValidBuiltInCode( $fallbackCode ),
"Invalid fallback code '$fallbackCode' in fallback sequence for '$code'" );
}
return $ret;
}
/**
* Get the ordered list of fallback languages, ending with the fallback language chain for the
* site language. The site fallback list begins with the site language itself.
*
* @since 1.35
* @param string $code Language code
* @return array [ fallbacks, site fallbacks ]
*/
public function getAllIncludingSiteLanguage( $code ) {
// Usually, we will only store a tiny number of fallback chains, so we cache in a member.
$cacheKey = "{$code}-{$this->siteLangCode}";
if ( !array_key_exists( $cacheKey, $this->fallbackCache ) ) {
$fallbacks = $this->getAll( $code );
if ( $code === $this->siteLangCode ) {
// Don't bother hitting the localisation cache a second time
$siteFallbacks = [ $code ];
} else {
// Append the site's fallback chain, including the site language itself
$siteFallbacks = $this->getAll( $this->siteLangCode );
array_unshift( $siteFallbacks, $this->siteLangCode );
// Eliminate any languages already included in the chain
$siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
}
$this->fallbackCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
}
return $this->fallbackCache[$cacheKey];
}
}