summaryrefslogtreecommitdiff
path: root/includes/LocalisationCache.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/LocalisationCache.php')
-rw-r--r--includes/LocalisationCache.php192
1 files changed, 153 insertions, 39 deletions
diff --git a/includes/LocalisationCache.php b/includes/LocalisationCache.php
index 3b1f45cc..d8e5d3a3 100644
--- a/includes/LocalisationCache.php
+++ b/includes/LocalisationCache.php
@@ -1,4 +1,24 @@
<?php
+/**
+ * Cache of the contents of localisation files.
+ *
+ * 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
+ */
define( 'MW_LC_VERSION', 2 );
@@ -90,7 +110,7 @@ class LocalisationCache {
'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
- 'digitGroupingPattern'
+ 'digitGroupingPattern', 'pluralRules', 'compiledPluralRules',
);
/**
@@ -98,7 +118,7 @@ class LocalisationCache {
* by a fallback sequence.
*/
static public $mergeableMapKeys = array( 'messages', 'namespaceNames',
- 'dateFormats', 'imageFiles', 'preloadedMessages',
+ 'dateFormats', 'imageFiles', 'preloadedMessages'
);
/**
@@ -134,6 +154,12 @@ class LocalisationCache {
*/
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;
/**
@@ -214,9 +240,9 @@ class LocalisationCache {
*/
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] ) ) {
@@ -236,9 +262,9 @@ class LocalisationCache {
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] ) ) {
@@ -343,10 +369,11 @@ class LocalisationCache {
/**
* Returns true if the cache identified by $code is missing or expired.
+ * @return bool
*/
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;
}
@@ -355,7 +382,7 @@ class LocalisationCache {
$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;
}
@@ -365,7 +392,7 @@ class LocalisationCache {
// 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;
}
@@ -460,9 +487,95 @@ class LocalisationCache {
} 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;
}
@@ -564,14 +677,12 @@ class LocalisationCache {
$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 ) {
@@ -584,7 +695,6 @@ class LocalisationCache {
if ( is_null( $coreData['fallback'] ) ) {
$coreData['fallback'] = $code === 'en' ? false : 'en';
}
-
if ( $coreData['fallback'] === false ) {
$coreData['fallbackSequence'] = array();
} else {
@@ -600,15 +710,11 @@ class LocalisationCache {
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;
@@ -633,7 +739,7 @@ class LocalisationCache {
$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;
}
}
@@ -663,19 +769,26 @@ class LocalisationCache {
$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.' );
}
@@ -793,8 +906,8 @@ class LocalisationCache {
interface LCStore {
/**
* Get a value.
- * @param $code Language code
- * @param $key Cache key
+ * @param $code string Language code
+ * @param $key string Cache key
*/
function get( $code, $key );
@@ -903,17 +1016,17 @@ class LCStore_DB implements LCStore {
}
if ( !$code ) {
- throw new MWException( __METHOD__.": Invalid language \"$code\"" );
+ throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
}
$this->dbw = wfGetDB( DB_MASTER );
try {
- $this->dbw->begin();
+ $this->dbw->begin( __METHOD__ );
$this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ );
} catch ( DBQueryError $e ) {
if ( $this->dbw->wasReadOnlyError() ) {
$this->readOnly = true;
- $this->dbw->rollback();
+ $this->dbw->rollback( __METHOD__ );
$this->dbw->ignoreErrors( false );
return;
} else {
@@ -934,7 +1047,7 @@ class LCStore_DB implements LCStore {
$this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ );
}
- $this->dbw->commit();
+ $this->dbw->commit( __METHOD__ );
$this->currentLang = null;
$this->dbw = null;
$this->batch = array();
@@ -947,7 +1060,7 @@ class LCStore_DB implements LCStore {
}
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(
@@ -1019,7 +1132,7 @@ class LCStore_CDB implements LCStore {
}
// Close reader to stop permission errors on write
- if( !empty($this->readers[$code]) ) {
+ if ( !empty( $this->readers[$code] ) ) {
$this->readers[$code]->close();
}
@@ -1037,14 +1150,14 @@ class LCStore_CDB implements LCStore {
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\"" );
+ throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
}
return "{$this->directory}/l10n_cache-$code.cdb";
}
@@ -1160,8 +1273,9 @@ class LocalisationCache_BulkLoad extends LocalisationCache {
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 );
}
}
-} \ No newline at end of file
+
+}