'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
- 'digitGroupingPattern'
+ 'digitGroupingPattern', 'pluralRules', 'compiledPluralRules',
);
/**
* by a fallback sequence.
*/
static public $mergeableMapKeys = array( 'messages', 'namespaceNames',
- 'dateFormats', 'imageFiles', 'preloadedMessages',
+ 'dateFormats', 'imageFiles', 'preloadedMessages'
);
/**
*/
static public $preloadedKeys = array( 'dateFormats', 'namespaceNames' );
+ /**
+ * Associative array of cached plural rules. The key is the language code,
+ * the value is an array of plural rules for that language.
+ */
+ var $pluralRules = null;
+
var $mergeableKeys = null;
/**
* for $wgLocalisationCacheConf.
*
* @param $conf Array
+ * @throws MWException
*/
function __construct( $conf ) {
global $wgCacheDirectory;
*/
public function getItem( $code, $key ) {
if ( !isset( $this->loadedItems[$code][$key] ) ) {
- wfProfileIn( __METHOD__.'-load' );
+ wfProfileIn( __METHOD__ . '-load' );
$this->loadItem( $code, $key );
- wfProfileOut( __METHOD__.'-load' );
+ wfProfileOut( __METHOD__ . '-load' );
}
if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
public function getSubitem( $code, $key, $subkey ) {
if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
!isset( $this->loadedItems[$code][$key] ) ) {
- wfProfileIn( __METHOD__.'-load' );
+ wfProfileIn( __METHOD__ . '-load' );
$this->loadSubitem( $code, $key, $subkey );
- wfProfileOut( __METHOD__.'-load' );
+ wfProfileOut( __METHOD__ . '-load' );
}
if ( isset( $this->data[$code][$key][$subkey] ) ) {
*/
public function isExpired( $code ) {
if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
- wfDebug( __METHOD__."($code): forced reload\n" );
+ wfDebug( __METHOD__ . "($code): forced reload\n" );
return true;
}
$deps = $this->store->get( $code, 'deps' );
- $keys = $this->store->get( $code, 'list', 'messages' );
+ $keys = $this->store->get( $code, 'list' );
$preload = $this->store->get( $code, 'preload' );
// Different keys may expire separately, at least in LCStore_Accel
if ( $deps === null || $keys === null || $preload === null ) {
- wfDebug( __METHOD__."($code): cache missing, need to make one\n" );
+ wfDebug( __METHOD__ . "($code): cache missing, need to make one\n" );
return true;
}
// anymore (e.g. uninstalled extensions)
// When this happens, always expire the cache
if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
- wfDebug( __METHOD__."($code): cache for $code expired due to " .
+ wfDebug( __METHOD__ . "($code): cache for $code expired due to " .
get_class( $dep ) . "\n" );
return true;
}
/**
* Initialise a language in this object. Rebuild the cache if necessary.
* @param $code
+ * @throws MWException
*/
protected function initLanguage( $code ) {
if ( isset( $this->initialisedLangs[$code] ) ) {
* Read a PHP file containing localisation data.
* @param $_fileName
* @param $_fileType
+ * @throws MWException
* @return array
*/
protected function readPHPFile( $_fileName, $_fileType ) {
} elseif ( $_fileType == 'aliases' ) {
$data = compact( 'aliases' );
} else {
- throw new MWException( __METHOD__.": Invalid file type: $_fileType" );
+ throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
+ }
+ return $data;
+ }
+
+ /**
+ * Get the compiled plural rules for a given language from the XML files.
+ * @since 1.20
+ */
+ public function getCompiledPluralRules( $code ) {
+ $rules = $this->getPluralRules( $code );
+ if ( $rules === null ) {
+ return null;
+ }
+ try {
+ $compiledRules = CLDRPluralRuleEvaluator::compile( $rules );
+ } catch( CLDRPluralRuleError $e ) {
+ wfDebugLog( 'l10n', $e->getMessage() . "\n" );
+ return array();
+ }
+ return $compiledRules;
+ }
+
+ /**
+ * Get the plural rules for a given language from the XML files.
+ * Cached.
+ * @since 1.20
+ */
+ public function getPluralRules( $code ) {
+ if ( $this->pluralRules === null ) {
+ $cldrPlural = __DIR__ . "/../languages/data/plurals.xml";
+ $mwPlural = __DIR__ . "/../languages/data/plurals-mediawiki.xml";
+ // Load CLDR plural rules
+ $this->loadPluralFile( $cldrPlural );
+ if ( file_exists( $mwPlural ) ) {
+ // Override or extend
+ $this->loadPluralFile( $mwPlural );
+ }
+ }
+ if ( !isset( $this->pluralRules[$code] ) ) {
+ return null;
+ } else {
+ return $this->pluralRules[$code];
+ }
+ }
+
+
+ /**
+ * Load a plural XML file with the given filename, compile the relevant
+ * rules, and save the compiled rules in a process-local cache.
+ */
+ protected function loadPluralFile( $fileName ) {
+ $doc = new DOMDocument;
+ $doc->load( $fileName );
+ $rulesets = $doc->getElementsByTagName( "pluralRules" );
+ foreach ( $rulesets as $ruleset ) {
+ $codes = $ruleset->getAttribute( 'locales' );
+ $rules = array();
+ $ruleElements = $ruleset->getElementsByTagName( "pluralRule" );
+ foreach ( $ruleElements as $elt ) {
+ $rules[] = $elt->nodeValue;
+ }
+ foreach ( explode( ' ', $codes ) as $code ) {
+ $this->pluralRules[$code] = $rules;
+ }
+ }
+ }
+
+ /**
+ * Read the data from the source files for a given language, and register
+ * the relevant dependencies in the $deps array. If the localisation
+ * exists, the data array is returned, otherwise false is returned.
+ */
+ protected function readSourceFilesAndRegisterDeps( $code, &$deps ) {
+ $fileName = Language::getMessagesFileName( $code );
+ if ( !file_exists( $fileName ) ) {
+ return false;
}
+ $deps[] = new FileDependency( $fileName );
+ $data = $this->readPHPFile( $fileName, 'core' );
+
+ # Load CLDR plural rules for JavaScript
+ $data['pluralRules'] = $this->getPluralRules( $code );
+ # And for PHP
+ $data['compiledPluralRules'] = $this->getCompiledPluralRules( $code );
+
+ $deps['plurals'] = new FileDependency( __DIR__ . "/../languages/data/plurals.xml" );
+ $deps['plurals-mw'] = new FileDependency( __DIR__ . "/../languages/data/plurals-mediawiki.xml" );
return $data;
}
* Load localisation data for a given language for both core and extensions
* and save it to the persistent cache store and the process cache
* @param $code
+ * @throws MWException
*/
public function recache( $code ) {
global $wgExtensionMessagesFiles;
$deps = array();
# Load the primary localisation from the source file
- $fileName = Language::getMessagesFileName( $code );
- if ( !file_exists( $fileName ) ) {
- wfDebug( __METHOD__.": no localisation file for $code, using fallback to en\n" );
+ $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
+ if ( $data === false ) {
+ wfDebug( __METHOD__ . ": no localisation file for $code, using fallback to en\n" );
$coreData['fallback'] = 'en';
} else {
- $deps[] = new FileDependency( $fileName );
- $data = $this->readPHPFile( $fileName, 'core' );
- wfDebug( __METHOD__.": got localisation for $code from source\n" );
+ wfDebug( __METHOD__ . ": got localisation for $code from source\n" );
# Merge primary localisation
foreach ( $data as $key => $value ) {
if ( is_null( $coreData['fallback'] ) ) {
$coreData['fallback'] = $code === 'en' ? false : 'en';
}
-
if ( $coreData['fallback'] === false ) {
$coreData['fallbackSequence'] = array();
} else {
foreach ( $coreData['fallbackSequence'] as $fbCode ) {
# Load the secondary localisation from the source file to
# avoid infinite cycles on cyclic fallbacks
- $fbFilename = Language::getMessagesFileName( $fbCode );
-
- if ( !file_exists( $fbFilename ) ) {
+ $fbData = $this->readSourceFilesAndRegisterDeps( $fbCode, $deps );
+ if ( $fbData === false ) {
continue;
}
- $deps[] = new FileDependency( $fbFilename );
- $fbData = $this->readPHPFile( $fbFilename, 'core' );
-
foreach ( self::$allKeys as $key ) {
if ( !isset( $fbData[$key] ) ) {
continue;
$used = false;
foreach ( $data as $key => $item ) {
- if( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) {
+ if ( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) {
$used = true;
}
}
$page = str_replace( ' ', '_', $page );
}
# Decouple the reference to prevent accidental damage
- unset($page);
+ unset( $page );
+
+ # If there were no plural rules, return an empty array
+ if ( $allData['pluralRules'] === null ) {
+ $allData['pluralRules'] = array();
+ }
+ if ( $allData['compiledPluralRules'] === null ) {
+ $allData['compiledPluralRules'] = array();
+ }
# Set the list keys
$allData['list'] = array();
foreach ( self::$splitKeys as $key ) {
$allData['list'][$key] = array_keys( $allData[$key] );
}
-
# Run hooks
wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData ) );
if ( is_null( $allData['namespaceNames'] ) ) {
- throw new MWException( __METHOD__.': Localisation data failed sanity check! ' .
+ throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' .
'Check that your languages/messages/MessagesEn.php file is intact.' );
}
}
if ( !$code ) {
- throw new MWException( __METHOD__.": Invalid language \"$code\"" );
+ throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
}
$this->dbw = wfGetDB( DB_MASTER );
}
if ( is_null( $this->currentLang ) ) {
- throw new MWException( __CLASS__.': must call startWrite() before calling set()' );
+ throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' );
}
$this->batch[] = array(
}
// Close reader to stop permission errors on write
- if( !empty($this->readers[$code]) ) {
+ if ( !empty( $this->readers[$code] ) ) {
$this->readers[$code]->close();
}
public function set( $key, $value ) {
if ( is_null( $this->writer ) ) {
- throw new MWException( __CLASS__.': must call startWrite() before calling set()' );
+ throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' );
}
$this->writer->set( $key, serialize( $value ) );
}
protected function getFileName( $code ) {
- if ( !$code || strpos( $code, '/' ) !== false ) {
- throw new MWException( __METHOD__.": Invalid language \"$code\"" );
+ if ( strval( $code ) === '' || strpos( $code, '/' ) !== false ) {
+ throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
}
return "{$this->directory}/l10n_cache-$code.cdb";
}
while ( count( $this->data ) > $this->maxLoadedLangs && count( $this->mruLangs ) ) {
reset( $this->mruLangs );
$code = key( $this->mruLangs );
- wfDebug( __METHOD__.": unloading $code\n" );
+ wfDebug( __METHOD__ . ": unloading $code\n" );
$this->unload( $code );
}
}
+
}